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

效果展示

在这里插入图片描述

初始想法:本来想直接四个输入框,然后监听输入满四个字符,自动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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值