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

Flutter Form表单切换输入框后所有输入框光标持续闪烁异常

Flutter Form表单切换后所有输入框光标持续闪烁问题修复

问题现象

应用内常规Form表单页面初始加载时,无输入框聚焦、无光标闪烁异常。但依次切换输入框(无论是否填写内容)后,所有输入框都会出现光标持续闪烁的问题,预期效果为仅当前聚焦的输入框显示闪烁光标,异常效果如下:
输入框光标异常闪烁截图
异常表单实现代码如下:

class RegisterView extends StatefulWidget {
  const RegisterView({Key? key}) : super(key: key);

  @override
  State<RegisterView> createState() => _RegisterViewState();
}

class _RegisterViewState extends State<RegisterView> {
  final _formKey = GlobalKey<FormState>();
  final FocusScopeNode _node = FocusScopeNode();
  String name = '';
  String email = '';
  String nif = '';
  String password = '';
  String _errorMessage = '';

  TextEditingController nameCtrl = TextEditingController();
  TextEditingController emailCtrl = TextEditingController();
  TextEditingController nifCtrl = TextEditingController();
  TextEditingController passwordCtrl = TextEditingController();


  @override
  void dispose() {
    _node.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    UserProvider userProvider =
        Provider.of<UserProvider>(context, listen: false);
    return Scaffold(
      appBar: AppBar(
        leading: const CustomBackButton(),
        title: const Text('Register',
            style: TextStyle(
                color: kPrimaryColor,
                fontSize: 16.0,
                fontWeight: FontWeight.bold)),
        centerTitle: true,
      ),
      body: GestureDetector(
        onTap: () {
          FocusScope.of(context).requestFocus(FocusNode());
        },
        child: SingleChildScrollView(
          child: Form(
            key: _formKey,
            child: FocusScope(
              node: _node,
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 10.0),
                child: Column(
                  children: [
                    const Padding(
                      padding: EdgeInsets.symmetric(vertical: 15.0),
                      child: Text(
                        'Please set your personal data:',
                        style:
                            TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(bottom: 10.0),
                      child: Row(
                        children: [
                          Text(_errorMessage,
                              style: const TextStyle(color: Colors.red)),
                        ],
                      ),
                    ),

                    TextFormField(
                      controller: nameCtrl,
                      keyboardType: TextInputType.number,
                      decoration: InputDecoration(
                        filled: true,
                        fillColor: Colors.grey[250],
                        contentPadding: const EdgeInsets.all(10.0),
                        hintText: 'Name',
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15),
                          borderSide: const BorderSide(
                            color: kPrimaryColor,
                            width: 2,
                          ),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15),
                          borderSide: const BorderSide(
                            color: kPrimaryColor,
                            width: 2,
                          ),
                        ),
                      ),
                      onChanged: (String data) {
                        setState(() {
                          name = data;
                        });
                      },
                      onEditingComplete: _node.nextFocus,
                    ),
                    const SizedBox(height: 15),
                    TextFormField(
                      controller: emailCtrl,
                      keyboardType: TextInputType.number,
                      decoration: InputDecoration(
                        filled: true,
                        fillColor: Colors.grey[250],
                        contentPadding: const EdgeInsets.all(10.0),
                        hintText: 'Email',
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15),
                          borderSide: const BorderSide(
                            color: kPrimaryColor,
                            width: 2,
                          ),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15),
                          borderSide: const BorderSide(
                            color: kPrimaryColor,
                            width: 2,
                          ),
                        ),
                      ),
                      onChanged: (String data) {
                        setState(() {
                          email = data;
                        });
                      },
                      validator: (String? value) {
                        if (value!.isEmpty) {
                          return 'Required';
                        } else if (!emailRegex.hasMatch(value)) {
                          return 'Please set a valid email';
                        }
                      },
                      onEditingComplete: _node.nextFocus,
                    ),
                    const SizedBox(height: 15),
                    TextFormField(
                      controller: nifCtrl,
                      keyboardType: TextInputType.number,
                      decoration: InputDecoration(
                        filled: true,
                        fillColor: Colors.grey[250],
                        contentPadding: const EdgeInsets.all(10.0),
                        hintText: 'NIF',
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15),
                          borderSide: const BorderSide(
                            color: kPrimaryColor,
                            width: 2,
                          ),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15),
                          borderSide: const BorderSide(
                            color: kPrimaryColor,
                            width: 2,
                          ),
                        ),
                      ),
                      onChanged: (String data) {
                         setState(() {
                     nif = data;
                   });
                      },
                     
                      onEditingComplete: _node.nextFocus,
                    ),
                    const SizedBox(height: 15),
                    TextFormField(
                      controller: passwordCtrl,
                      keyboardType: TextInputType.number,
                      decoration: InputDecoration(
                        filled: true,
                        fillColor: Colors.grey[250],
                        contentPadding: const EdgeInsets.all(10.0),
                        hintText: 'Password',
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15),
                          borderSide: const BorderSide(
                            color: kPrimaryColor,
                            width: 2,
                          ),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15),
                          borderSide: const BorderSide(
                            color: kPrimaryColor,
                            width: 2,
                          ),
                        ),
                      ),
                      onChanged: (String data) {
                        setState(() {
                          password = data;
                        });
                      },
                      validator: (String? value) {
                        if (value!.isEmpty) {
                          return 'Campo obrigatório';
                        } else if (value.length < 6) {
                          return 'Password deve conter pelo menos 6 caracteres';
                        }
                      },
                      onEditingComplete: _node.nextFocus,
                    ),
                    const SizedBox(height: 15),
                    RoundedButton(
                      bgColor: kPrimaryColor,
                      padding: const EdgeInsets.symmetric(
                          vertical: 10.0, horizontal: 10),
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: [
                          userProvider.isLoading
                              ? const SpinKitFadingCircle(
                                  color: Colors.white,
                                  size: 25.0,
                                )
                              : const Text(
                                  'Save',
                                  style: TextStyle(
                                    fontSize: 16,
                                    color: Colors.white,
                                    fontWeight: FontWeight.w700,
                                  ),
                                ),
                        ],
                      ),
                      onPressed: () {
                        if (_formKey.currentState!.validate()) {
                          print('Ok to register');
                        }
                      },
                    )
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

问题原因

手动创建FocusScopeNode管理焦点时,点击空白区域失焦调用的是根FocusScope的requestFocus方法,没有正确解绑自定义FocusScope下所有输入框的焦点状态,导致Flutter框架错误判定多个输入框同时处于可编辑激活状态,最终出现多光标同时闪烁的渲染异常。

修复方案

  • 方案1:统一使用自定义的FocusScopeNode处理失焦
    把空白区域点击的失焦逻辑改为调用持有的_node对象解绑焦点,修改GestureDetector的onTap回调:
    onTap: () {
      _node.unfocus();
      // 若unfocus后仍有异常,可追加这行强制取消焦点
      // _node.requestFocus(FocusNode());
    },
    
  • 方案2:移除手动创建的FocusScopeNode,直接使用Flutter自动注入的FocusScope
    无特殊焦点管理需求时,可删掉手动实例化的_node相关代码,所有焦点操作统一用FocusScope.of(context)完成:
    • 删除final FocusScopeNode _node = FocusScopeNode();声明
    • 删除dispose方法里的_node.dispose();
    • 移除包裹表单内容的FocusScope组件
    • 把所有onEditingComplete: _node.nextFocus改为onEditingComplete: () => FocusScope.of(context).nextFocus()
    • 空白区域点击失焦逻辑保留原有写法即可正常工作

额外优化点

代码里所有输入框的keyboardType都设置成了TextInputType.number,但姓名、邮箱、密码字段都不是纯数字输入场景,需要对应修改为正确的键盘类型:

  • 姓名字段:TextInputType.text
  • 邮箱字段:TextInputType.emailAddress
  • 密码字段:TextInputType.visiblePassword配合obscureText: true

内容的提问来源于stack exchange,提问作者JS_LnMstr

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 17:45:38