Flutter 验证码输入框

20秒前 327阅读

前言:

验证码输入框很常见:处理不好 bug也会比较多 想实现方法很多,这里列举一种完美方式,完美兼容 软键盘粘贴方式

效果如下:

Flutter 验证码输入框

之前使用 uniapp 的方式实现过一次 两种方式(原理相同):

input 验证码 密码 输入框_input密码输入框-CSDN博客文章浏览阅读3.9k次,点赞3次,收藏6次。前言:uniapp 在做需求的时候,经常会遇到;验证码输入框 或者 密码输框 自定义样式输入框 或者 格式化显示 银行卡 手机号码等等:这里总结了两种 常用的实现方式;从这两种实现方式 其实也能延伸出其他的显示 方式;先看样式: 自己实现 光标闪烁动画第一种:可以识别 获得焦点 失去焦点第一种实现的思路: 实际上就是,下层的真实 input 负责响应系统的输入,上面一层负责显示 应为输入框在手机端会 出现长按 学着 复制等等 输入框自带属..._input密码输入框Flutter 验证码输入框https://blog.csdn.net/nicepainkiller/article/details/124384995?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171723341916800226511048%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171723341916800226511048&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-124384995-null-null.nonecase&utm_term=input&spm=1018.2226.3001.4450

实现原理拆解:

输入框区域我们分割成两层:

  • 6个黄色的区域 仅仅做展示,中间的黑色是一个动画 模拟光标闪烁 或者 展示 输入的数字
  • 最上层盖一个 输入框控件 接收输入事件,设置透明度 0.00001,设置不支持长按 选取复制,仅仅支持数字

    Flutter 验证码输入框

    这样一来就很明了, 逻辑也很简单

     具体实现:

    • 要实现 软键盘的 填充事件,所以我们需要动态监听 输入事件
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        // 自动弹出软键盘
        Future.delayed(Duration.zero, () {
          FocusScope.of(context).requestFocus(_focusNode);
        });
        // 监听粘贴事件
        _textEditingController.addListener(() {
          if (Clipboard.getData('text/plain') != null) {
            Clipboard.getData('text/plain').then((value) {
              if (value != null && value.text != null) {
                if (value.text!.isNotEmpty && value.text!.length == 6) {
                  if (RegExp(AppRegular.numberAll).firstMatch(value.text!) !=
                      null) {
                    _textEditingController.text = value!.text!;
                    //取完值 置为 null
                    Clipboard.setData(const ClipboardData(text: ''));
                    //设置输入框光标到末尾 防止某些情况下 光标跑到前面,键盘无法删除输入字符
                    _textEditingController.selection = TextSelection.fromPosition(
                      TextPosition(offset: _textEditingController.text.length),
                    );
                  }
                }
              }
            });
          }
          setState(() {
            _arrayCode = List.filled(widget.length, '');
            for (int i = 0; i  
    • 输入框的设置,禁止长按

      child: TextField(
        enableInteractiveSelection: false, // 禁用长按复制功
        maxLength: widget.length,
        focusNode: _focusNode,
        maxLines: 1,
        controller: _textEditingController,
        style: AppTextStyle.textStyle_32_333333,
        inputFormatters: [InputFormatter(AppRegular.numberAll)],
        decoration: const InputDecoration(
          focusedBorder: OutlineInputBorder(
              borderSide:
                  BorderSide(width: 0, color: Colors.transparent)),
          disabledBorder: OutlineInputBorder(
              borderSide:
                  BorderSide(width: 0, color: Colors.transparent)),
          enabledBorder: OutlineInputBorder(
              borderSide:
                  BorderSide(width: 0, color: Colors.transparent)),
          border: OutlineInputBorder(
              borderSide:
                  BorderSide(width: 0, color: Colors.transparent)),
          counterText: '', //取消文字计数器
        ),
      )
    • 页面动画的展示,FadeTransition 为了性能优化到我们动画缩小到最小范围

      class InputFocusWidget extends StatefulWidget {
        const InputFocusWidget({Key? key}) : super(key: key);
        @override
        State createState() => _InputFocusWidgetState();
      }
      class _InputFocusWidgetState extends State
          with TickerProviderStateMixin {
        late AnimationController controller;
        late Animation animation;
        @override
        void initState() {
          // TODO: implement initState
          super.initState();
          controller = AnimationController(
              duration: const Duration(milliseconds: 600), vsync: this);
          animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
          controller.repeat(min: 0, max: 1, reverse: true);
        }
        @override
        void dispose() {
          controller.dispose();
          // TODO: implement dispose
          super.dispose();
        }
        @override
        Widget build(BuildContext context) {
          return FadeTransition(
            opacity: animation,
            child: Container(
              color: Colors.green,
              width: double.infinity,
              height: double.infinity,
            ),
          );
        }
      }

      完整代码:

       因为里面使用到我自己封装的一些工具,用的时候需要你转成自己的

      import 'package:flutter/material.dart';
      import 'package:flutter/services.dart';
      import 'package:game/utils/app_screen.dart';
      import 'package:game/wrap/extension/extension.dart';
      import 'package:game/wrap/overlay/app_overlay.dart';
      import '../const/app_regular.dart';
      import '../const/app_textStyle.dart';
      import 'input_formatter.dart';
      class InputWithCode extends StatefulWidget {
        final int length;
        final ValueChanged onComplete;
        const InputWithCode(
            {required this.length, required this.onComplete, Key? key})
            : super(key: key);
        @override
        State createState() => _InputWithCodeState();
      }
      class _InputWithCodeState extends State {
        final TextEditingController _textEditingController = TextEditingController();
        bool _triggerState = false;
        late List _arrayCode = List.filled(widget.length, '');
        final FocusNode _focusNode = FocusNode();
        @override
        void initState() {
          // TODO: implement initState
          super.initState();
          // 自动弹出软键盘
          Future.delayed(Duration.zero, () {
            FocusScope.of(context).requestFocus(_focusNode);
          });
          // 监听粘贴事件
          _textEditingController.addListener(() {
            if (Clipboard.getData('text/plain') != null) {
              Clipboard.getData('text/plain').then((value) {
                if (value != null && value.text != null) {
                  if (value.text!.isNotEmpty && value.text!.length == 6) {
                    if (RegExp(AppRegular.numberAll).firstMatch(value.text!) !=
                        null) {
                      _textEditingController.text = value!.text!;
                      Clipboard.setData(const ClipboardData(text: ''));
                      _textEditingController.selection = TextSelection.fromPosition(
                        TextPosition(offset: _textEditingController.text.length),
                      );
                    }
                  }
                }
              });
            }
            setState(() {
              _arrayCode = List.filled(widget.length, '');
              for (int i = 0; i  MapEntry(
                            index,
                            Container(
                              width: 80.cale,
                              height: 80.cale,
                              margin: EdgeInsets.symmetric(horizontal: 10.cale),
                              decoration: BoxDecoration(
                                border: Border(
                                  bottom: BorderSide(
                                    width: 3.cale,
                                    color: value != ''
                                        ? Colors.amberAccent
                                        : Colors.amberAccent.withOpacity(0.5),
                                  ),
                                ),
                              ),
                              child: index != _textEditingController.value.text.length
                                  ? Center(
                                      child: Text(
                                        value,
                                        style: AppTextStyle.textStyle_40_1A1A1A_Bold,
                                      ),
                                    )
                                  : Center(
                                      child: SizedBox(
                                        width: 3.cale,
                                        height: 40.cale,
                                        child: const InputFocusWidget(),
                                      ),
                                    ),
                            ),
                          ),
                        )
                        .values
                        .toList(),
                  ),
                ),
                Opacity(
                  opacity: 0.0001,
                  child: SizedBox(
                    height: double.infinity,
                    width: double.infinity,
                    child: TextField(
                      enableInteractiveSelection: false, // 禁用长按复制功
                      maxLength: widget.length,
                      focusNode: _focusNode,
                      maxLines: 1,
                      controller: _textEditingController,
                      style: AppTextStyle.textStyle_32_333333,
                      inputFormatters: [InputFormatter(AppRegular.numberAll)],
                      decoration: const InputDecoration(
                        focusedBorder: OutlineInputBorder(
                            borderSide:
                                BorderSide(width: 0, color: Colors.transparent)),
                        disabledBorder: OutlineInputBorder(
                            borderSide:
                                BorderSide(width: 0, color: Colors.transparent)),
                        enabledBorder: OutlineInputBorder(
                            borderSide:
                                BorderSide(width: 0, color: Colors.transparent)),
                        border: OutlineInputBorder(
                            borderSide:
                                BorderSide(width: 0, color: Colors.transparent)),
                        counterText: '', //取消文字计数器
                      ),
                    ),
                  ),
                ),
              ],
            ),
          );
        }
      }
      class InputFocusWidget extends StatefulWidget {
        const InputFocusWidget({Key? key}) : super(key: key);
        @override
        State createState() => _InputFocusWidgetState();
      }
      class _InputFocusWidgetState extends State
          with TickerProviderStateMixin {
        late AnimationController controller;
        late Animation animation;
        @override
        void initState() {
          // TODO: implement initState
          super.initState();
          controller = AnimationController(
              duration: const Duration(milliseconds: 600), vsync: this);
          animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
          controller.repeat(min: 0, max: 1, reverse: true);
        }
        @override
        void dispose() {
          controller.dispose();
          // TODO: implement dispose
          super.dispose();
        }
        @override
        Widget build(BuildContext context) {
          return FadeTransition(
            opacity: animation,
            child: Container(
              color: Colors.green,
              width: double.infinity,
              height: double.infinity,
            ),
          );
        }
      }
      
      使用:
      •  控件名称:InputWithCode
      •  length:验证码长度
      • onComplete: 输入完成回调
        Container(
          child: InputWithCode(
            length: 6,
            onComplete: (code) => {
              print('InputWithCode:$code'),
            },
          ),
          width: double.infinity,
          height: 200.cale,
        ),
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]