当前位置: 首页 > news >正文

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 initStatesuper.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!;//取完值 置为 nullClipboard.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);@overrideState<InputFocusWidget> createState() => _InputFocusWidgetState();
    }class _InputFocusWidgetState extends State<InputFocusWidget>with TickerProviderStateMixin {late AnimationController controller;late Animation<double> animation;@overridevoid initState() {// TODO: implement initStatesuper.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);}@overridevoid dispose() {controller.dispose();// TODO: implement disposesuper.dispose();}@overrideWidget 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);@overrideState<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();@overridevoid initState() {// TODO: implement initStatesuper.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;}});}@overrideWidget 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);@overrideState<InputFocusWidget> createState() => _InputFocusWidgetState();
}class _InputFocusWidgetState extends State<InputFocusWidget>with TickerProviderStateMixin {late AnimationController controller;late Animation<double> animation;@overridevoid initState() {// TODO: implement initStatesuper.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);}@overridevoid dispose() {controller.dispose();// TODO: implement disposesuper.dispose();}@overrideWidget 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.lryc.cn/news/360505.html

相关文章:

  • 如何从0到设计一个CRM系统
  • Numba 的 CUDA 示例 (2/4):穿针引线
  • 项目的各个阶段如何编写标准的Git commit消息
  • Python课设-学生信息管理系统
  • openssl 常用命令demo
  • 【Linux】Linux基本指令2
  • springboot+vue+mybatis博物馆售票系统+PPT+论文+讲解+售后
  • java—MyBatis框架
  • 如何使用Spring Cache优化后端接口?
  • 大话C语言:第21篇 数组
  • transfomer中attention为什么要除以根号d_k
  • iperf3带宽压测工具使用
  • [数据集][目标检测]焊接处缺陷检测数据集VOC+YOLO格式3400张8类别
  • 2024华为OD机试真题-剩余银饰的重量-C++(C卷D卷)
  • 糖果促销【百度之星】/思维
  • 【python学习】安装Anaconda后,如何进行环境管理(命令行操作及图形化操作Anaconda Navigator)及包管理
  • HTML大雪纷飞
  • 问界新M7 Ultra仅售28.98万元起,上市即交付
  • 【Java数据结构】详解LinkedList与链表(四)
  • ssm汉服文化平台网站
  • 如何让 LightRoom 每次导入照片后不自动弹出 SD 卡 LR
  • elasticdump和ESM
  • Java扩展机制:SPI与Spring.factories详解
  • iPhone 语言编程:深入探索与无限可能
  • css动态导航栏鼠标悬停特效
  • Vue中使用axios先获取头像上传参数然后上传图片到服务器-demo
  • Win11环境下Android Studio中Flutter开发环境构建(逐步解决)
  • Thread Servlet思考
  • 电源滤波器怎么选用
  • 终于更新了!时隔一年niushop多商户b2b2c的新补丁v5.0.2终于发布了,一起看看有啥新变化