Flutter多TextFormField点击Done键键盘不消失且光标回退问题
Flutter 表单焦点与字体颜色问题修复
问题分析
1. Done按钮点击后键盘无法正常收起
每次NumberInputField执行build时,都会判断isFirstField并请求焦点。点击Done按钮触发UI刷新时,第一个输入框会被再次请求焦点,导致键盘刚收起又弹出。
2. 提交后TextFormField字体颜色不更新
原代码将表单实例存储在_form变量中,仅在_shouldSetFormVisible为true时创建一次。后续_isAnswerCorrect状态变化时,已创建的表单实例不会重新构建,无法获取最新状态值。
修复方案
- 移除表单实例变量:不在状态中存储
_form,而是在build方法中根据实时状态直接渲染表单,确保状态变化时Widget树能重新构建。 - 优化自动聚焦逻辑:仅在表单首次显示时请求第一个输入框的焦点,避免每次build都触发。通过
WidgetsBinding.instance.addPostFrameCallback在页面渲染完成后执行聚焦操作。 - 简化状态更新逻辑:移除嵌套的setState,避免不必要的状态刷新。
修复后的完整代码
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: const MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { bool _isAnswerCorrect = false; bool _isFormVisible = false; bool _hasInitiatedFocus = false; final _formKey = GlobalKey<FormState>(); final FocusNode _focusNode1 = FocusNode(); final FocusNode _focusNode2 = FocusNode(); final FocusNode _focusNode3 = FocusNode(); final TextEditingController _textEditingController1 = TextEditingController(); final TextEditingController _textEditingController2 = TextEditingController(); final TextEditingController _textEditingController3 = TextEditingController(); void _answerIsCorrect() { setState(() { _isAnswerCorrect = true; }); } void _showForm() { Future.delayed(const Duration(milliseconds: 100), () { setState(() { _isFormVisible = true; _hasInitiatedFocus = false; }); }); } @override void dispose() { _focusNode1.dispose(); _focusNode2.dispose(); _focusNode3.dispose(); _textEditingController1.dispose(); _textEditingController2.dispose(); _textEditingController3.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // 仅在表单首次显示时请求焦点 if (_isFormVisible && !_hasInitiatedFocus) { WidgetsBinding.instance.addPostFrameCallback((_) { FocusScope.of(context).requestFocus(_focusNode1); setState(() { _hasInitiatedFocus = true; }); }); } return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( child: const Text('Show form'), onPressed: _showForm, ), const SizedBox(height: 20), if (_isFormVisible) SizedBox( width: 300, child: Column( children: [ Form( key: _formKey, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: NumberInputField( isAnswerCorrect: _isAnswerCorrect, isLastField: false, currentFieldFocusNode: _focusNode1, nextFieldFocusNode: _focusNode2, textEditingController: _textEditingController1, ), ), const SizedBox(width: 20), Expanded( child: NumberInputField( isAnswerCorrect: _isAnswerCorrect, isLastField: false, currentFieldFocusNode: _focusNode2, nextFieldFocusNode: _focusNode3, textEditingController: _textEditingController2, ), ), const SizedBox(width: 20), Expanded( child: NumberInputField( isAnswerCorrect: _isAnswerCorrect, isLastField: true, currentFieldFocusNode: _focusNode3, nextFieldFocusNode: _focusNode3, textEditingController: _textEditingController3, ), ), ], ), ), const SizedBox(height: 20), ElevatedButton( child: const Text('Submit'), onPressed: _answerIsCorrect, ), ], ), ), ], ), ), ); } } class NumberInputField extends StatelessWidget { final bool isAnswerCorrect; final bool isLastField; final FocusNode currentFieldFocusNode; final FocusNode nextFieldFocusNode; final TextEditingController textEditingController; const NumberInputField({ super.key, required this.isAnswerCorrect, required this.isLastField, required this.currentFieldFocusNode, required this.nextFieldFocusNode, required this.textEditingController, }); @override Widget build(BuildContext context) { final fontColor = isAnswerCorrect ? Colors.black : Colors.blue; return TextFormField( controller: textEditingController, focusNode: currentFieldFocusNode, onChanged: (val) { if (val.length == 1 && !isLastField) { FocusScope.of(context).requestFocus(nextFieldFocusNode); } }, style: TextStyle(color: fontColor), keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: '?', ), ); } }
关键修改点说明
- 移除
_form变量,改用if (_isFormVisible)直接渲染表单,确保状态变化时Widget能重新构建,解决字体颜色不更新问题。 - 添加
_hasInitiatedFocus标志位,结合addPostFrameCallback仅在表单首次显示时请求焦点,避免每次build都触发聚焦,解决Done按钮点击后键盘反复弹出的问题。 - 简化
NumberInputField,移除不必要的isFirstField参数,聚焦逻辑移到父组件处理,减少子组件的副作用。 - 完善dispose方法,释放所有FocusNode和TextEditingController资源,避免内存泄漏。
内容的提问来源于stack exchange,提问作者vacosti
相关产品推荐
相关产品推荐

