Flutter 验证码输入框

前言:

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

效果如下:

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

input 验证码 密码 输入框_input密码输入框-CSDN博客文章浏览阅读3.9k次,点赞3次,收藏6次。前言:uniapp 在做需求的时候,经常会遇到;验证码输入框 或者 密码输框 自定义样式输入框 或者 格式化显示 银行卡 手机号码等等:这里总结了两种 常用的实现方式;从这两种实现方式 其实也能延伸出其他的显示 方式;先看样式: 自己实现 光标闪烁动画第一种:可以识别 获得焦点 失去焦点第一种实现的思路: 实际上就是,下层的真实 input 负责响应系统的输入,上面一层负责显示 应为输入框在手机端会 出现长按 学着 复制等等 输入框自带属..._input密码输入框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,设置不支持长按 选取复制,仅仅支持数字

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

 具体实现:

  • 要实现 软键盘的 填充事件,所以我们需要动态监听 输入事件
    
    @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<String>.filled(widget.length, '');
          for (int i = 0; i < _textEditingController.value.text.length; i++) {
            _arrayCode[i] = _textEditingController.value.text.substring(i, i + 1);
          }
        });
        if (_textEditingController.value.text.length == 6) {
          //防止重复触发 回调事件
          if (!_triggerState) {
            _triggerState = true;
            AppScreen.showToast('输入完成:${_textEditingController.value.text}');
            widget.onComplete(_textEditingController.value.text);
          }
        } else {
          _triggerState = false;
        }
      });
    }
  • 输入框的设置,禁止长按

    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<InputFocusWidget> createState() => _InputFocusWidgetState();
    }
    
    class _InputFocusWidgetState extends State<InputFocusWidget>
        with TickerProviderStateMixin {
      late AnimationController controller;
      late Animation<double> 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<String> onComplete;
  const InputWithCode(
      {required this.length, required this.onComplete, Key? key})
      : super(key: key);

  @override
  State<InputWithCode> createState() => _InputWithCodeState();
}

class _InputWithCodeState extends State<InputWithCode> {
  final TextEditingController _textEditingController = TextEditingController();
  bool _triggerState = false;
  late List<String> _arrayCode = List<String>.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<String>.filled(widget.length, '');
        for (int i = 0; i < _textEditingController.value.text.length; i++) {
          _arrayCode[i] = _textEditingController.value.text.substring(i, i + 1);
        }
      });
      if (_textEditingController.value.text.length == 6) {
        if (!_triggerState) {
          _triggerState = true;
          AppScreen.showToast('输入完成:${_textEditingController.value.text}');
          widget.onComplete(_textEditingController.value.text);
        }
      } else {
        _triggerState = false;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: double.infinity,
      child: Stack(
        children: [
          Center(
            child: Row(
              children: _arrayCode
                  .asMap()
                  .map(
                    (index, value) => 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<InputFocusWidget> createState() => _InputFocusWidgetState();
}

class _InputFocusWidgetState extends State<InputFocusWidget>
    with TickerProviderStateMixin {
  late AnimationController controller;
  late Animation<double> 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,
),

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/671199.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【TB作品】MSP430F5529,单片机,电子秒表,秒表

硬件 MSP430F5529开发板7针0.96寸OLED /* OLED引脚分配 绿色板子DO(SCLK)------P4.3D1(DATA)------P4.0RES-----------P3.7DC------------P8.2CS------------P8.1 */ 程序功能 该程序是一个用C语言编写的&#xff0c;用于msp430f5529微控制器上的简单电子秒表应用。它使用…

多源 BFS 详解

目录 一、多源与单源的区别 二、例题练习 2.1 例题1&#xff1a;01 矩阵 2.2 例题2&#xff1a;飞地的数量 2.3 例题3&#xff1a;地图中的最高点 2.4 例题4&#xff1a;地图分析 一、多源与单源的区别 单源最短路问题如何解决已经在上篇博客给出BFS 解决最短路问题&am…

Qt | Qt 资源简介(rcc、qmake)

1、资源系统是一种独立于平台的机制,用于在应用程序的可执行文件中存储二进制文件(前面所讨论的数据都存储在外部设备中)。若应用程序始终需要一组特定的文件(比如图标),则非常有用。 2、资源系统基于 qmake,rcc(Qt 的资源编译器,用于把资源转换为 C++代码)和 QFile …

Java扩展机制:SPI与Spring.factories详解

一、SPI SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。 整体机制图如下: Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。 系统设计的各个抽象,往往有很多不…

【车载开发系列】汽车开发常用工具说明

【车载开发系列】汽车开发常用工具说明 【车载开发系列】汽车开发常用工具说明 【车载开发系列】汽车开发常用工具说明一. CANbedded二. Davinci Configurator Pro三. Davinci Developer-AUTOSAR软件组件设计工具四. MICROSAR五. MICROSAR OS六. CANdelaStudio七. Volcano VSB八…

css动态导航栏鼠标悬停特效

charset "utf-8"; /*科e互联特效基本框架CSS*/ body, ul, dl, dd, dt, ol, li, p, h1, h2, h3, h4, h5, h6, textarea, form, select, fieldset, table, td, div, input {margin:0;padding:0;-webkit-text-size-adjust: none} h1, h2, h3, h4, h5, h6{font-size:12px…

Nodejs-- 网络编程

网络编程 构建tcp服务 TCP tcp全名为传输控制协议。再osi模型中属于传输层协议。 tcp是面向连接的协议&#xff0c;在传输之前需要形成三次握手形成会话 只有会话形成了&#xff0c;服务端和客户端才能想发送数据&#xff0c;在创建会话的过程中&#xff0c;服务端和客户…

【计算机毕业设计】353微信小程序零食批发交易管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

如何进行时间管理

1.一项调查显示&#xff0c;每个人在睡觉上花费的平均时间大约为25年&#xff0c;这无疑是非常重要的。但排名第二的项目却让人大跌眼镜——看电视是8.3年。接下来分别是工作7.5年、吃饭6年、等待和家务各5年、身体护理4.1年、做白日梦4年&#xff0c;等等。 远远落后的是读书时…

ChatTTS+Python编程搞定语音报时小程序

文字转语音神器Python编程搞定语音报时小程序 今天一个好哥们发了一个文字转语音的AI神器的短视频。这个神器的网站是[ChatTTS - Text-to-Speech for Conversational Scenarios][https://chattts.com/]&#xff0c;如下图所示&#xff1a; 这个开源项目可以从github.com上下载…

[数据集][目标检测]轮胎检测数据集VOC+YOLO格式439张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;439 标注数量(xml文件个数)&#xff1a;439 标注数量(txt文件个数)&#xff1a;439 标注类别…

智慧商砼搅拌车安监运营管理的创新实践

随着城市化进程的加速&#xff0c;商砼搅拌车作为城市建设的重要设备&#xff0c;其安全管理与运营效率直接关系到工程质量和施工进度。近年来&#xff0c;通过引入先进的4G无线视频智能车载终端套件&#xff0c;我们实现了对商砼搅拌车的高精度定位、实时音视频调度、实时油量…

队列——一种操作受限的线性表

队列 队列&#xff08;Queue&#xff09;简称队&#xff0c;也是一种操作受限的线性表&#xff0c;只允许在表的一端进行插入&#xff0c;而在表的另一端进行删除。向队列中插入元素称为入队或进队&#xff0c;删除元素称为出队或离队。队列中的元素是先进先出&#xff08;Fir…

C++设计模式-桥接模式

运行在VS2022&#xff0c;x86&#xff0c;Debug下。 29. 桥接模式 桥接模式将抽象与实现分离&#xff0c;使二者可以独立地变化。 应用&#xff1a;如在游戏开发中&#xff0c;多个角色和多个武器交叉组合时。可以使用桥接模式&#xff0c;定义角色抽象类&#xff0c;武器抽象…

注册北京个体工商户条件和办理时间

在北京这座充满活力的城市中&#xff0c;每天都有无数的创业者怀揣着梦想&#xff0c;踏上创业之路。然而&#xff0c;对于许多初次接触企业注册的人来说&#xff0c;往往对注册流程和时间感到困惑。特别是选择代理服务时&#xff0c;更希望了解一个大概的时间范围。那么&#…

Flutter开发效率提升1000%,Flutter Quick教程之对Widget进行删除,剪切,粘贴

一&#xff0c;删除操作 1&#xff0c;首先我们选中要删除的Widget。 2&#xff0c;在左边的侧边栏&#xff0c;点击删除按钮&#xff0c;即可完成对组件的删除操作。 二&#xff0c;剪切。剪切是相同的道理&#xff0c;都是先选中&#xff0c;再点击对应的按钮。 1&#xff…

拿捏AVL(C++)

文章目录 前言AVL树介绍模拟实现框架查找插入验证删除完整代码 性能分析总结 前言 在本篇文章中我&#xff0c;我们将会介绍一下有关AVL树的相关内容&#xff0c;并且对一些接口进行模拟实现。 AVL树介绍 为什么会有AVL树呢&#xff1f;&#xff1f; 我们在之前学习二叉树时…

UI的学习(一)

UI的学习(一) 文章目录 UI的学习(一)UIlabelUIButtonUIButton的两种形式UIButton的事件触发 UIView多个视图之间的关系 UIWindowUIViewController一个视图推出另一个视图 定时器和视图移动UISwitchUISlider和UIProgressSlid步进器与分栏控制器UITextFieldUIScrollView有关实现它…

【Python打包成exe】

Python打包成exe 前言一、理论知识打底二、实操开始----pyinstaller【Base环境下】【这是一个失败案例】规规矩矩 总结 前言 先放点参考 这个字多&#xff0c;写得很详细⇨用 Pyinstaller 模块将 Python 程序打包成 exe 文件&#xff08;全网最全面最详细&#xff0c;万字详述…

C语言王国——内存函数

目录 1 memcpy函数 1.1 函数表达式 1.2 函数模拟 2 memmove函数 2.1 函数的表达式 2.2 函数模拟 3 memset函数 3.1 函数的表达式 3.2 函数的运用 4 memcmp函数 4.1函数的表达式&#xff1a; 4.2 函数的运用 5 结论 接上回我们讲了C语言的字符和字符串函数&#…