Flutter中onFocusChange无法捕获TextField最新输入值问题求助
背景
我正在开发一个基础任务管理应用,addTaskFormView里包含任务标题和描述两个TextField组件,每个TextField都单独封装在独立文件中,通过父组件addTaskFormView传入的回调函数来更新父组件中TextEditingController的状态。
问题
我希望用户点击屏幕其他区域离开TextField时能获取输入值,所以用Focus组件包裹了TextField,在onFocusChange回调里调用父组件传过来的方法。但现在点击提交按钮时,虽然调用了FocusScope.of(context).unfocus(),理论上应该触发onFocusChange,但实际却拿不到最新的输入值——第一次点击提交没输出,第二次点击才会输出上一次的内容,比如输入"first"点提交没反应,输入"second"点提交才输出"first"。
代码
addTaskFormView.dart
class addTaskFormView extends StatefulWidget { var taskIdentifier; final TextEditingController titleController = TextEditingController(); final TextEditingController descController = TextEditingController(); addTaskFormView({Key? key, this.taskIdentifier}); @override State<addTaskFormView> createState() => _addTaskFormViewState(); } class _addTaskFormViewState extends State<addTaskFormView> { String taskTitle = ''; String description =''; var viewModelAddTask; @override void initState() { super.initState(); viewModelAddTask = Provider.of<addTaskFormViewModel>(context, listen: false); } @override Widget build(BuildContext context) { setTitle(String? value) { print("CALL BACK HAS BEEN CALLED"); setState(() { this.taskTitle = value!; widget.titleController.text = value; }); } setDescription(String? value) { setState(() { this.description = value!; widget.descController.text = value; }); } return GestureDetector( onTap: () => FocusManager.instance.primaryFocus?.unfocus(), child: Scaffold( appBar: AppBar( title: const Text('Add a new task'), ), body: Column( children:[ Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ buildTaskTitle( callBackFunction: setTitle, titleController: widget.titleController, ), buildTaskDesc( callBackFunction: setDescription, descController: widget.descController ), ElevatedButton(onPressed: (){ //submit data to hive FocusScope.of(context).unfocus(); print("task title: $taskTitle"); print("task title: $description"); }, child: const Text('Submit')) ] ), ), ] ), ), ); } }
buildTaskDesc.dart
class buildTaskDesc extends StatefulWidget { const buildTaskDesc({Key? key, required this.callBackFunction, required this.descController}); final Function callBackFunction; final TextEditingController descController; @override State<buildTaskDesc> createState() => _buildTaskDescState(); } class _buildTaskDescState extends State<buildTaskDesc> { String description = ''; @override Widget build(BuildContext context) { return Focus( canRequestFocus: false, onFocusChange: (hasFocus) { hasFocus ? print('title has focus') : widget.callBackFunction(widget.descController.text); }, child: TextField( key: const Key('task_description'), controller: widget.descController, decoration: InputDecoration( labelText: 'Description', hintText: 'Enter task description', border: const OutlineInputBorder(), suffixIcon: widget.descController.text.isEmpty ? Container(width: 0) : IconButton( icon: const Icon(Icons.close), onPressed: () => widget.descController.clear(), ), ), style: const TextStyle(fontSize: 14), maxLines: 15, minLines: 7, keyboardType: TextInputType.text, textInputAction: TextInputAction.done, ), ); } }
额外尝试
buildTaskTitle的逻辑和buildTaskDesc完全一致,这里就不贴代码了。我试过用GestureDetector包裹Scaffold,通过onTap调用unfocus;也试过在ElevatedButton里嵌套GestureDetector调用unfocus,但都没解决问题。
解决方案
问题根源
调用FocusScope.of(context).unfocus()后,onFocusChange的回调执行时机早于TextField将输入内容同步到TextEditingController的时机,所以此时拿到的还是旧值。
可行解决方式
实时同步输入内容:在
TextField中添加onChanged回调,每次输入变化时直接调用父组件的回调更新状态。修改buildTaskDesc中的TextField:TextField( // 原有代码不变 onChanged: (value) { widget.callBackFunction(value); }, )这样父组件能实时获取最新输入值,无需依赖失焦事件。
延迟获取控制器值:如果必须依赖失焦事件,在
onFocusChange中延迟执行回调,确保TextField完成内容同步:onFocusChange: (hasFocus) { if (!hasFocus) { Future.delayed(const Duration(milliseconds: 100), () { widget.callBackFunction(widget.descController.text); }); } },监听控制器变化:在父组件的
initState中给TextEditingController添加监听,直接同步最新值:@override void initState() { super.initState(); viewModelAddTask = Provider.of<addTaskFormViewModel>(context, listen: false); widget.titleController.addListener(() { setState(() { taskTitle = widget.titleController.text; }); }); widget.descController.addListener(() { setState(() { description = widget.descController.text; }); }); }这种方式最可靠,提交时无需先失焦,直接打印控制器的
text属性即可。
内容的提问来源于stack exchange,提问作者NoviceCoder

