Flutter 模仿银行卡号等长号码输入分段显示

tech2024-08-11  49

效果展示

初始想法:本来想直接四个输入框,然后监听输入满四个字符,自动next焦点,但是粘贴显示的交互不太友好,加上四个textController + 四个FocusNode总感觉太冗余

最终想法:模拟一个输入框和光标,隐藏真正的输入框及禁用长按操作,自实现粘贴功能(粘贴会清除原来的字符串,完全替换成粘贴板的字符串)

全部代码:

/* * * 输入框 * 允许十六位字母+数字格式 * 分开显示 每组四个字符 * */ class LongCode extends StatefulWidget { final BuildContext ctx; final TextEditingController textEditingController; final FocusNode focusNode; LongCode({@required this.ctx, this.textEditingController, this.focusNode}); @override _LongCodeState createState() => _LongCodeState(); } class _LongCodeState extends State<LongCode> with SingleTickerProviderStateMixin { String code = ''; // 模拟光标动画 Animation<double> animation; AnimationController controller; bool isShowPaste = false;//是否展示粘贴按钮 @override void initState() { super.initState(); //光标动画 controller = AnimationController(duration: const Duration(seconds: 1), vsync: this); animation = new Tween(begin: 1.0, end: 0.0).animate(controller); controller.repeat(); // 监听输入框变化 // 不使用onchange是因为:clear()不会触发onchange,但监听可以监听到clear() widget.textEditingController.addListener(() { if (this.mounted) { setState(() { this.code = widget.textEditingController.text; }); } }); } @override void dispose() { controller.dispose(); super.dispose(); } // 光标样式动画 _cursor() { return AnimatedBuilder( animation: animation, builder: (BuildContext ctx, Widget child) { return Container( width: 30.w, alignment: Alignment.center, child: Container( width: 2.w, height: 34.w, color: Color.fromRGBO(54, 54, 54, animation.value), ), ); }, ); } // 每个item样式 _getTextItem(String text, {hasCursor = false}) { List<Widget> col = []; for (var i = 0; i < text.length; i++) { col.add(Container(width: 30.w, alignment: Alignment.center, child: Text(text[i]))); } if (text.length < 4 && hasCursor) { col.add(_cursor()); } return Container( width: 122.w, height: 56.w, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.w), border: Border.all(color: Color(0xFFCBCBCB), style: BorderStyle.solid, width: 1.w), ), child: Row( children: col, ), ); } // 总体样式 分成四个小框 _getTextRow() { List<Widget> col = []; bool hasCursor = false; for (var i = 0; i < 4; i++) { String text = this.code; String subText = ''; hasCursor = false; if (text.length > i * 4 + 4) { subText = text.substring(i * 4, i * 4 + 4); } else if (text.length > i * 4) { subText = text.substring(i * 4); } if (text.length ~/ 4 == i) { hasCursor = true; } col.add(_getTextItem(subText, hasCursor: hasCursor)); if (i != 3) { col.add(Padding( padding: EdgeInsets.symmetric(horizontal: 7.w), child: Text( '-', style: TextStyle( fontSize: 30.sp, height: 42 / 30, color: Color(0xFFCBCBCB), fontWeight: FontWeight.normal, decoration: TextDecoration.none), ), )); } } return col; } // 隐形输入框 _getTextField() { return Opacity( opacity: 0, child: TextField( focusNode: widget.focusNode, controller: widget.textEditingController, decoration: new InputDecoration( hintText: '', isDense: true, contentPadding: EdgeInsets.only(bottom: 0.w), ), style: TextStyle(color: Colors.transparent), cursorWidth: 0, toolbarOptions: ToolbarOptions(),// 禁用长按toolbar功能 textInputAction: TextInputAction.done, maxLines: 1, minLines: 1, inputFormatters: [ LengthLimitingTextInputFormatter(16), //长度限制 WhitelistingTextInputFormatter(RegExp("[a-zA-Z]|[0-9]")), //只能输入字母和数字 ], ), ); } //自实现粘贴按钮 _getPasteBtn() { return GestureDetector( onTap: () { setState(() { this.isShowPaste = false; }); Clipboard.getData('text/plain').then((value) { setState(() { this.isShowPaste = false; widget.textEditingController.text = value.text; //手动设置输入框光标位置到字符串尾部,否则删除键会报错 widget.textEditingController.selection = TextSelection.fromPosition(TextPosition(offset: value.text.length)); }); }); }, child: Container( alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(10.w)), color: Colors.white, border: Border.all(color: Color(0xFFCBCBCB), style: BorderStyle.solid, width: 1.w), boxShadow: [ BoxShadow( color: Color.fromRGBO(216, 183, 183, 0.16), offset: Offset(0.w, 10.w), //阴影xy轴偏移量 blurRadius: 26.w, //阴影模糊程度 ), ], ), width: 96.w, height: 60.w, child: Text( '粘贴', style: TextStyle( fontSize: 24.sp, height: 36 / 24, color: Color(0xFF464646), fontWeight: FontWeight.normal, decoration: TextDecoration.none), ), ), ); } @override Widget build(BuildContext context) { this.code = widget.textEditingController.text; return Stack( overflow: Overflow.visible, children: <Widget>[ GestureDetector( onTap: () { widget.focusNode.requestFocus(); }, onLongPress: () { setState(() { this.isShowPaste = true; }); }, child: Column( children: <Widget>[ //兑换码显示框 Row( mainAxisAlignment: MainAxisAlignment.center, children: _getTextRow(), ), //隐形输入框 _getTextField(), ], ), ), //粘贴按钮 Positioned( top: 30.w, left: 262.w, child: Offstage( offstage: !isShowPaste, child: _getPasteBtn(), ), ), ], ); } }
最新回复(0)