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

Flutter复用TextFormField异常:点击一个输入框时另一个也激活

解决TextFormField复用组件焦点冲突问题

问题原因

你遇到的核心问题是两个输入框共用了同一个FocusNode实例。FocusNode是Flutter中单独管理输入框焦点状态的对象,当多个输入框绑定同一个FocusNode时,焦点的激活/失活会同时作用在所有绑定的输入框上,导致点击邮箱输入框时密码框也跟着被激活。

解决方案

步骤1:创建独立的FocusNode实例

在你的Stateful Widget的State类中,为每个输入框声明并初始化单独的FocusNode,同时记得在组件销毁时释放资源:

class _LoginPageState extends State<LoginPage> {
  late final TextEditingController _userEmail;
  late final TextEditingController _userPassword;
  // 为每个输入框创建独立的FocusNode
  late final FocusNode _emailFocusNode;
  late final FocusNode _passwordFocusNode;
  late Color _color;

  @override
  void initState() {
    super.initState();
    _userEmail = TextEditingController();
    _userPassword = TextEditingController();
    // 初始化FocusNode
    _emailFocusNode = FocusNode();
    _passwordFocusNode = FocusNode();
    _color = ...; // 你的颜色初始化逻辑
  }

  @override
  void dispose() {
    _userEmail.dispose();
    _userPassword.dispose();
    // 释放FocusNode资源,避免内存泄漏
    _emailFocusNode.dispose();
    _passwordFocusNode.dispose();
    super.dispose();
  }
}

步骤2:调用组件时传入对应FocusNode

修改login_page.dart中调用FormFields的代码,分别为每个输入框传入对应的FocusNode:

FormFields.textFormFieldWidget(
  controller: _userEmail,
  hintText: "Email Address",
  hintStyle: CustomTextStyle.reusableTextStyle(
    customColor: const Color(0xFF999EA1),
    customFontSize: 14,
    customFontWeight: FontWeight.w600),
  borderColor: const Color(0xFFCDD1E0),
  colorFilled: _color,
  textFieldFocus: _emailFocusNode, // 传入邮箱专属FocusNode
),
const SizedBox(height: 12),
FormFields.textFormFieldWidget(
  controller: _userPassword,
  isPassword: true,
  hintText: "Password",
  hintStyle: CustomTextStyle.reusableTextStyle(
    customColor: const Color(0xFF999EA1),
    customFontSize: 14,
    customFontWeight: FontWeight.w600),
  borderColor: const Color(0xFFCDD1E0),
  colorFilled: _color,
  suffixIcon: Icons.remove_red_eye,
  textFieldFocus: _passwordFocusNode, // 传入密码专属FocusNode
),

可选优化:修复复用组件的逻辑问题

你的reusable_widget.dart中有两处逻辑错误,建议修正:

  1. readOnly判断:原代码readOnly == null ? false : true会导致传入false时仍返回true,应改为readOnly ?? false
  2. enabled判断:原代码null == enabled ? true : false可简化为enabled ?? true
  3. obscureText可简化为isPassword ?? false

修正后的核心代码:

TextFormField(
  autovalidateMode: AutovalidateMode.onUserInteraction,
  controller: controller,
  readOnly: readOnly ?? false,
  obscureText: isPassword ?? false,
  decoration: InputDecoration(
    fillColor: colorFilled,
    filled: true,
    focusedBorder: OutlineInputBorder(
      borderRadius: const BorderRadius.all(Radius.circular(10.0)),
      borderSide: BorderSide(
        color: borderSide ?? const Color(0xFF6949FF),
        width: 1.15
      ),
    ),
    enabledBorder: OutlineInputBorder(
      borderRadius: const BorderRadius.all(Radius.circular(10.0)),
      borderSide: BorderSide(
        color: borderSide ?? const Color(0xFFCDD1E0),
        width: 1.15,
      ),
    ),
    hintText: hintText ?? '',
    hintStyle: hintStyle ?? TextStyle(),
    helperText: helpText ?? '',
    prefixIcon: prefixIcon != null ? Icon(prefixIcon) : null,
    suffix: suffixIcon != null ? Icon(suffixIcon) : null,
    enabled: enabled ?? true,
  ),
  focusNode: textFieldFocus,
  textAlignVertical: TextAlignVertical.center,
)

可选:添加焦点自动切换逻辑

如果需要输入完邮箱后自动跳到密码框,可以在邮箱输入框的回调中添加焦点切换逻辑:

// 先在reusable_widget.dart的参数中添加onFieldSubmitted
static textFormFieldWidget({
  // ... 原有参数
  ValueChanged<String>? onFieldSubmitted,
}) {
  return TextFormField(
    // ... 原有代码
    onFieldSubmitted: onFieldSubmitted,
  );
}

// 在login_page.dart中调用时传入
FormFields.textFormFieldWidget(
  // ... 其他参数
  textFieldFocus: _emailFocusNode,
  onFieldSubmitted: (_) {
    FocusScope.of(context).requestFocus(_passwordFocusNode);
  },
),

内容的提问来源于stack exchange,提问作者jpg.hue

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 12:50:39