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