Flutter中如何向聚焦TextField写入自定义数字盘输入内容
实现方案
核心逻辑分三步:修复点击数字按钮抢占输入框焦点的问题、全局追踪当前激活的输入框控制器、在光标位置插入输入内容,全程不需要遍历焦点树,稳定性高。
先修复现有代码的基础问题
你当前代码里把所有TextEditingController定义在build方法内是严重错误,每次页面重建都会生成新的控制器,会导致输入内容丢失、内存泄漏,所有控制器、焦点节点必须定义在State类内部,在initState中初始化,在dispose中释放。
第一步:追踪当前聚焦的输入框控制器
- 在
_MainViewState类中新增变量,存储当前激活的控制器,以及所有输入框对应的焦点节点:
// 所有控制器、焦点节点都定义在类内部,不要放在build方法里 late TextEditingController sideAController; late TextEditingController sideBController; late TextEditingController sideCController; late TextEditingController alphaController; late TextEditingController bettaController; late TextEditingController gammaController; late FocusNode sideAFocus; late FocusNode sideBFocus; late FocusNode sideCFocus; late FocusNode alphaFocus; late FocusNode bettaFocus; late FocusNode gammaFocus; // 全局持有当前处于聚焦状态的输入框控制器 TextEditingController? _activeController;
- 在
initState中初始化所有对象,给每个焦点节点加监听,一旦对应输入框获得焦点,就更新_activeController为当前输入框的控制器:
@override void initState() { super.initState(); // 初始化控制器 sideAController = TextEditingController(); sideBController = TextEditingController(); sideCController = TextEditingController(); alphaController = TextEditingController(); bettaController = TextEditingController(); gammaController = TextEditingController(); // 统一绑定焦点监听逻辑 void bindFocus(FocusNode node, TextEditingController controller) { node.addListener(() { if (node.hasFocus) _activeController = controller; }); } // 初始化焦点节点并绑定监听 sideAFocus = FocusNode(); sideBFocus = FocusNode(); sideCFocus = FocusNode(); alphaFocus = FocusNode(); bettaFocus = FocusNode(); gammaFocus = FocusNode(); bindFocus(sideAFocus, sideAController); bindFocus(sideBFocus, sideBController); bindFocus(sideCFocus, sideCController); bindFocus(alphaFocus, alphaController); bindFocus(bettaFocus, bettaController); bindFocus(gammaFocus, gammaController); } // 页面销毁时释放所有资源,避免内存泄漏 @override void dispose() { sideAController.dispose(); sideBController.dispose(); sideCController.dispose(); alphaController.dispose(); bettaController.dispose(); gammaController.dispose(); sideAFocus.dispose(); sideBFocus.dispose(); sideCFocus.dispose(); alphaFocus.dispose(); bettaFocus.dispose(); gammaFocus.dispose(); super.dispose(); }
- 修改
InputValue组件,新增焦点节点入参,绑定到内部的TextField上:
class InputValue extends StatelessWidget { final String name; final String units; final double width; final TextEditingController textFieldController; final FocusNode focusNode; // 新增焦点节点参数 InputValue({ required this.name, required this.units, this.width = 70, required this.textFieldController, required this.focusNode, }); @override Widget build(BuildContext context) { String labelText = name + units; return Container( padding: EdgeInsets.symmetric(vertical: 5), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( margin: EdgeInsets.symmetric(vertical: 5), height: 30, width: width, child: TextField( focusNode: focusNode, // 绑定传入的焦点节点 keyboardType: TextInputType.none, controller: textFieldController, maxLines: 1, style: TextStyle(fontSize: 11), decoration: InputDecoration( contentPadding: EdgeInsets.fromLTRB(5, 1, 5, 1.0), labelText: labelText, enabledBorder: OutlineInputBorder( borderSide: const BorderSide(width: 1, color: Colors.blue), borderRadius: BorderRadius.circular(5), ), focusedBorder: OutlineInputBorder( borderSide: const BorderSide(width: 1, color: Colors.green), borderRadius: BorderRadius.circular(5), )), ), ), ], ), ); } }
- 在
build方法里使用InputValue时,传入对应初始化好的焦点节点即可,比如边a的输入框传sideAFocus,角度α的输入框传alphaFocus。
第二步:修改数字盘按钮,避免点击时抢占焦点
不要用GestureDetector监听数字按钮点击,它会参与焦点竞争,点击时会让输入框失焦。改用Listener监听原始指针事件,不会触发焦点转移,同时去掉多余的numberPadController,直接通过回调传递点击的符号:
class NumberPadButton extends StatelessWidget { final String symbol; final Function(String) onInput; NumberPadButton({required this.symbol, required this.onInput}); @override Widget build(BuildContext context) { return Expanded( child: Listener( onPointerDown: (_) => onInput(symbol), child: Container( margin: EdgeInsets.all(1), padding: EdgeInsets.all(2), decoration: BoxDecoration( color: buttonBackgroundColor, borderRadius: BorderRadius.circular(1)), child: Center( child: Text( symbol, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w700), )), ), ), ); } }
第三步:实现数字输入逻辑
在_MainViewState中写统一的输入处理方法,支持在光标位置插入内容,处理选中替换的场景,体验和系统键盘一致:
void _handleNumberInput(String input) { if (_activeController == null) return; final controller = _activeController!; final text = controller.text; final selection = controller.selection; // 处理退格逻辑,如果需要的话 if (input == 'backspace') { if (text.isEmpty) return; String newText; int newCursorPos; if (selection.start != selection.end) { // 有选中内容,直接删除选中部分 newText = text.replaceRange(selection.start, selection.end, ''); newCursorPos = selection.start; } else if (selection.start > 0) { // 没有选中内容,删除光标前一位 newText = text.replaceRange(selection.start - 1, selection.start, ''); newCursorPos = selection.start - 1; } else { return; } controller.text = newText; controller.selection = TextSelection.collapsed(offset: newCursorPos); return; } // 普通数字/字符输入 String newText; int newCursorPos; if (selection.start >= 0 && selection.end >= 0) { // 有选中内容,替换选中部分 newText = text.replaceRange(selection.start, selection.end, input); newCursorPos = selection.start + input.length; } else { // 没有光标信息,直接追加到末尾 newText = text + input; newCursorPos = newText.length; } controller.text = newText; controller.selection = TextSelection.collapsed(offset: newCursorPos); }
最后给每个数字按钮传入这个回调即可:
NumberPadButton( symbol: '1', onInput: _handleNumberInput, )
其余数字、符号、功能按钮都按这个方式绑定,计算类按钮单独写业务逻辑即可。
不要尝试通过
FocusScope.of(context).focusedChild遍历焦点树找对应控制器,实现繁琐且适配成本高,自己通过焦点监听维护当前激活控制器是稳定性最高、代码量最少的方案。
内容的提问来源于stack exchange,提问作者KiriLL Sabko
相关产品推荐
相关产品推荐

