Flutter开发:如何用单个TextForm为输入框每个字符加下划线实现手机号验证
Flutter 实现单字符独立下划线的手机号输入效果
核心结论
- 不需要拆分多个独立输入框,完全可以通过单个
TextField/TextFormField实现目标交互 - 拆分多输入框的方案需要额外处理焦点跳转、退格回退、满值自动跳位等边界逻辑,兼容成本高;单输入框的方案可以完全复用原生输入的状态管理能力,稳定性更强
实现思路
将单个原生输入框改造为纯事件接收层:隐藏其自带的文字渲染、默认装饰元素,仅保留接收键盘输入、维护光标位置、处理输入校验的基础能力;在其下方叠放一层自定义视觉组件,负责渲染每个单字符占位、对应位置的字符内容、专属下划线的状态样式,两层通过同一个TextEditingController做实时状态同步。
具体实现步骤
- 配置透明化的原生输入框
对输入框做如下基础配置,去掉所有原生渲染元素:final TextEditingController _phoneController = TextEditingController(); TextField( controller: _phoneController, keyboardType: TextInputType.phone, maxLength: 11, // 和手机号位数对齐 // 隐藏原生输入的文字渲染,保持透明 style: const TextStyle(color: Colors.transparent), // 移除默认装饰、长度计数器、原生下划线 decoration: const InputDecoration( counterText: "", border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, contentPadding: EdgeInsets.zero, ), // 限制仅可输入数字,符合手机号校验规则 inputFormatters: [FilteringTextInputFormatter.digitsOnly], ) - 实现自定义单字符占位视觉组件
横向排列对应位数的等宽占位块,每个块独立承担内容展示、下划线绘制的能力,根据输入状态切换下划线样式:class CharPlaceholderRow extends StatelessWidget { final String inputContent; final int totalCount; const CharPlaceholderRow({ super.key, required this.inputContent, required this.totalCount, }); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(totalCount, (index) { // 取当前位置对应的输入字符,无输入则展示空内容 final char = index < inputContent.length ? inputContent[index] : ""; return Container( width: 32, margin: const EdgeInsets.symmetric(horizontal: 6), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( char, style: const TextStyle(fontSize: 22, color: Colors.black87), ), const SizedBox(height: 6), // 每个占位独立的下划线,根据位置切换状态 Container( height: 2, color: index == inputContent.length ? Theme.of(context).primaryColor // 光标所在位置的下划线高亮 : Colors.grey.shade300, // 其余位置默认浅灰色下划线 ) ], ), ); }), ); } } - 叠层组装组件
使用Stack将自定义占位层放在下层,透明输入框放在上层,让用户的点击、输入操作全部由上层原生输入框承接:Stack( alignment: Alignment.center, children: [ // 下层:自定义视觉展示层 ListenableBuilder( listenable: _phoneController, builder: (context, _) { return CharPlaceholderRow( inputContent: _phoneController.text, totalCount: 11, ); }, ), // 上层:透明输入框,覆盖整个区域承接输入事件 SizedBox( height: 48, // 适配占位组件的整体高度,保证点击热区覆盖完整 child: TextField( controller: _phoneController, // 前面步骤配置的透明样式规则 ), ) ], )
优化注意点
- 调整上层透明输入框的尺寸,让点击热区完全覆盖所有占位块的范围,避免点击空隙无法唤起键盘的问题
- 可根据交互需求调整下划线的状态逻辑:已输入位置的下划线可设置为深灰色或主题色,未输入位置为浅灰色,光标停留位置可做加粗高亮
- 输入控制器需要添加监听,文本变化时自动触发下层占位组件的刷新,保证内容和输入状态实时同步
内容的提问来源于stack exchange,提问作者aeviou
相关产品推荐
相关产品推荐

