Flutter 验证码输入框

news2024/10/7 18:28:12

前言:

验证码输入框很常见:处理不好 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,
),

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GLM-4-9B性能究竟如何?

GLM-4-9B 开源系列模型 前言 自 2023 年 3 月 14 日 ChatGLM-6B 开源以来&#xff0c;GLM 系列模型受到广泛认可。特别是在 ChatGLM3-6B 开源后&#xff0c;针对让小模型能够拥有更为强大的能力这一目标&#xff0c;GLM 技术团队展开了诸多的探索性工作。历经将近半年的探索历程…

为什么要做与运算?网关如何和ip做与运算?

在计算机网络中&#xff0c;“与运算”是一个基本而重要的概念&#xff0c;尤其在IP地址和子网掩码的处理中起着关键作用。本文将解释为什么要进行与运算&#xff0c;以及网关如何和IP地址进行与运算。 为什么要做与运算&#xff1f; 1. 确定网络地址 与运算&#xff08;AND…

PhpSpreadsheet表格导出

个人笔记记录 使用PhpSpreadsheet 导出excel。 多重表头生成excel 表 //读取数据库public function demo1(){// 连接数据库$config Config::get(databaseedc);$db Db::connect($config);$data $db->name("xxxx")->alias(a)->field(main_header, sub_hea…

在 Win系统安装 Ubuntu20.04子系统 WSL2 (默认是C盘,第7步开始迁移到D盘,也可以不迁移)

1、简介 WSL在Windows 10上原生运行Linux二进制可执行文件&#xff0c;不用单独安装虚拟机。 WSL2是WSL的第二个版本&#xff0c;提供了与WSL相比的显著性能改进和完全的系统呼叫兼容性。通过运行Linux内核在一个轻量级虚拟机&#xff08;VM&#xff09;中实现。 2、安装 电…

SAP PP学习笔记14 - MTS(Make-to-Stock) 按库存生产(策略10),以及生产计划的概要

上面讲了SAP里面的基础知识&#xff0c;BOM&#xff0c;作业手顺&#xff08;工艺路线&#xff09;&#xff0c;作业区&#xff08;工作中心&#xff09;&#xff0c;MRP&#xff0c;MPS等概念&#xff0c;现在该到用的时候了。 SAP PP学习笔记07 - 简单BOM&#xff0c;派生BO…

将单列数据帧转换成多列数据帧

文章目录 1. 查看数据文件2. 读取数据文件得到单例数据帧3. 将单列数据帧转换成多列数据帧 在本次实战中&#xff0c;我们的目标是将存储在HDFS上的以逗号分隔的文本文件student.txt转换为结构化的Spark DataFrame。首先&#xff0c;使用spark.read.text读取文件&#xff0c;得…

金融行业数字化上云及信创改造过程中的一些问题及解决方案|合集①

Q&#xff1a;对金融机构来讲&#xff0c;什么是一切业务运行的前提&#xff1f; 金融机构的业务连续性对系统的稳定性要求极高。任何系统故障都可能导致严重的业务中断和经济损失。因此&#xff0c;金融机构需要IT基础架构能够提供高稳定性的服务&#xff0c;确保业务的连续运…

[沫忘录]MySQL InnoDB引擎

[沫忘录]MySQL InnoDB引擎 逻辑存储结构 InnoDB采用 “表、段&#xff0c;区&#xff0c; 页、行” 这样的层级结构进行存储。 **表空间(tablespace)**ibd文件&#xff0c;主要用于存储记录、索引等数据&#xff0c;一个mysql实例可有多个表空间&#xff0c;甚至能通过innodb…

.NET周刊【6月第1期 2024-06-02】

国内文章 一文带你了解.NET能做什么&#xff1f; https://www.cnblogs.com/Can-daydayup/p/18214473 .NET是一个免费、开源、跨平台的开发平台框架&#xff0c;广泛应用于桌面、Web、移动、云服务、游戏、物联网、大数据和人工智能等领域开发。它支持C#、Visual Basic、F#等…

SSL代码签名最佳实践

代码签名就是软件发布者使用全球可信的证书颁发机构CA颁发的代码签名证书对软件代码进行签名&#xff0c;由此来验证软件开发者的真实身份&#xff0c;确保软件代码的完整性和可信任性。然而&#xff0c;攻击者一直试图渗透代码签名&#xff0c;意将恶意软件嵌入可信代码中。由…

finalshell刚连上就断,这个参数你注意到了吗

在实际应用中可能一不下心弄错一个参数就会让你的finalshell刚连上就断&#xff0c;如下图所示。 1、进入ssh目录下&#xff0c;修改ssh_config文件 2、修改UseDNS no,并把前面的#去掉。 注&#xff1a;如果在ssh_config文件见不到UseDNS yes ,可以打开sshd_config,他们是在…

视频怎么压缩变小?推荐三个压缩方法

视频怎么压缩变小&#xff1f;在数字时代&#xff0c;视频已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;随着视频质量的提升&#xff0c;视频文件的大小也随之增加&#xff0c;这给存储和分享带来了不小的挑战。幸运的是&#xff0c;市面上有许多视频压缩软件…

Java学习笔记 集合的使用

在实际的项目中开发过程中&#xff0c;会有很多的对象&#xff0c;如何高效、方便的管理这些对象&#xff0c;是影响程序性能与可维护性的重要环节。在Java语言中为这个问题提供了一套完美的解决方案&#xff0c;也就是接下来要介绍的集合框架。 1.1 集合框架的结构 从Collect…

什么是 Batch Normalization 批标准化和全连接层

Batch Normalization 神经元在经过激活函数之后会处于饱和状态&#xff0c;无论后续怎么变化都不会再起作用。 每一层都会进行batch normalization的处理&#xff01; without normalization 会导致数据分布再饱和区 全连接层&#xff1a; 全连接层(fully connected layers&a…

DPDK基础组件一(mbuf、ring、pktmbuf_pool)

一、rte_mbuf 此部分转自:https://zhuanlan.zhihu.com/p/616314276 1.mbuf结构 mbuf是报文中的描素的结构体,是整个转发过程中最核心的数据结构之一。主要针对于mbuf的常用API与基本原理做一个简单的介绍。 mbuf:报文内存存储结构,存储在mempool中mempool:使用环形缓冲…

【kubernetes】k8s集群中的ingress(对外服务)规则详解

目录 一、Ingress 简介 1.1service的作用 1.2外部访问方案 (四种&#xff09;&#x1f339;&#x1f339;&#x1f339; 部署externalIPs 1.3Ingress 是什么 二、Ingress 组成&#x1f339;&#x1f339;&#x1f339; 三、Ingress 工作原理&#x1f431;&#x1f…

一维时间序列信号的小波时间散射变换(MATLAB 2021)

小波散射变换的目的在于获取第一层次的特征信息&#xff0c;即免疫平移、轻微形变的信息。而低通的滤波器能够获取输入信号的概貌&#xff0c;获取反映其整体大尺度特征的信息&#xff0c;以图像为例&#xff0c;由低通滤波器选取的信号对于图像的平移、伸缩、旋转等局部变化有…

【QT5】<总览二> QT信号槽、对象树及样式表

文章目录 前言 一、QT信号与槽 1. 信号槽连接模型 2. 信号槽介绍 3. 自定义信号槽 二、不使用UI文件编程 三、QT的对象树 四、添加资源文件 五、样式表的使用 六、QSS文件的使用 前言 承接【QT5】&#xff1c;总览一&#xff1e; QT环境搭建、快捷键及编程规范。若存…

计算机发展史 | 从起源到现代技术的演进

computer | Evolution from origins to modern technology 今天没有参考资料哈哈 PPT&#xff1a;&#xff08;评论区&#xff1f;&#xff09; 早期计算工具 算盘 -算盘是一种手动操作的计算辅助工具&#xff0c;起源于中国&#xff0c;迄今已有2600多年的历史&#xff0c;是…

告别鼠标:蓝牙无线安卓模拟鼠标,绘图板,手写板操作电脑PC端,卡卡罗特也说好,儿童节快乐

家人们&#xff0c;上链接了&#xff1a;https://download.csdn.net/download/jasonhongcn/89387887 横屏模式&#xff1a; 竖屏模式&#xff1a; 操作说明&#xff1a; 1. 手势滑动模拟鼠标移动 2. 界面如果有滚动条&#xff0c;右手指按紧&#xff0c;通过左手指移动实现…