You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter多TextFormField点击Done键键盘不消失且光标回退问题

Flutter 表单焦点与字体颜色问题修复

问题分析

1. Done按钮点击后键盘无法正常收起

每次NumberInputField执行build时,都会判断isFirstField并请求焦点。点击Done按钮触发UI刷新时,第一个输入框会被再次请求焦点,导致键盘刚收起又弹出。

2. 提交后TextFormField字体颜色不更新

原代码将表单实例存储在_form变量中,仅在_shouldSetFormVisible为true时创建一次。后续_isAnswerCorrect状态变化时,已创建的表单实例不会重新构建,无法获取最新状态值。

修复方案

  1. 移除表单实例变量:不在状态中存储_form,而是在build方法中根据实时状态直接渲染表单,确保状态变化时Widget树能重新构建。
  2. 优化自动聚焦逻辑:仅在表单首次显示时请求第一个输入框的焦点,避免每次build都触发。通过WidgetsBinding.instance.addPostFrameCallback在页面渲染完成后执行聚焦操作。
  3. 简化状态更新逻辑:移除嵌套的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 23:42:34