You need to enable JavaScript to run this app. Flutter登录页切换输入框焦点时已输入内容消失问题
优惠活动
大模型
产品
解决方案
定价
更多

Flutter登录页切换输入框焦点时已输入内容消失问题

内容丢失的核心原因
  • 你在StatelessWidget的build方法内部直接创建TextEditingController实例。build方法会在页面焦点切换、键盘弹收、尺寸变化时多次触发,每次触发都会生成全新的空控制器,旧控制器存储的输入内容会被直接丢弃,绑定的TextField会自动关联新的空控制器,直观表现就是切换焦点时输入内容消失。
  • 自定义PasswordInput组件中,手动在onChanged回调里给绑定的TextEditingController赋值的逻辑属于重复操作。Controller和TextField绑定后,输入内容会自动同步到Controller,手动赋值会额外带来光标跳变、输入状态异常的问题。
修复步骤

1. 将LoginScreen转换为StatefulWidget,把控制器的生命周期和State绑定

绝对不要在build方法内初始化需要长期持有状态的控制器,要把控制器的初始化移到State类中,和State的生命周期保持一致,同时在页面销毁时释放控制器避免内存泄漏:

class LoginScreen extends StatefulWidget {
  static const routeName = '/login';
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  // 仅在State初始化时生成一次控制器实例
  final _username = TextEditingController();
  final _password = TextEditingController();

  @override
  void dispose() {
    // 页面销毁时释放资源
    _username.dispose();
    _password.dispose();
    super.dispose();
  }

  void _clearValues() {
    _username.clear();
    _password.clear();
  }

  @override
  Widget build(BuildContext context) {
    final deviceSize = MediaQuery.of(context).size;
    // 原有页面布局逻辑全部平移到这个build方法内即可,无需改动结构
    return Scaffold(
      appBar: AppBar(
        title: const Text('Duckanji'),
      ),
      body: SingleChildScrollView(
        scrollDirection: Axis.vertical,
        child: Container(
          child: Column(
            children: [
              Container(
                margin: const EdgeInsets.symmetric(vertical: 30),
                child: Image.asset(
                  'assets/img/auth_logo.png',
                  width: deviceSize.width * 0.5,
                ),
              ),
              const SizedBox(height: 10),
              Container(
                height: 100,
                margin: const EdgeInsets.symmetric(horizontal: 20),
                child: TextField(
                  textInputAction: TextInputAction.next,
                  controller: _username,
                  maxLength: 35,
                  decoration: const InputDecoration(
                    prefixIcon: Icon(Icons.account_box_rounded),
                    border: OutlineInputBorder(),
                    labelText: 'Username',
                  ),
                ),
              ),
              PasswordInput(_password),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  FlatButton(
                    padding: const EdgeInsets.symmetric(
                      vertical: 10,
                      horizontal: 15,
                    ),
                    color: Theme.of(context).primaryColor,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(5),
                    ),
                    onPressed: () {
                      print(_username.text);
                      print(_password.text);
                      _clearValues();
                    },
                    child: const Text(
                      'Login',
                      style: TextStyle(
                        fontSize: 20,
                        color: Colors.white,
                      ),
                    ),
                  ),
                  FlatButton(
                    padding: const EdgeInsets.symmetric(
                      vertical: 10,
                      horizontal: 15,
                    ),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(5),
                      side: BorderSide(
                        color: Theme.of(context).primaryColor,
                      ),
                    ),
                    onPressed: () {},
                    child: Text(
                      'Register',
                      style: TextStyle(
                        fontSize: 20,
                        color: Theme.of(context).primaryColor,
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

2. 删除PasswordInput组件中多余的onChanged赋值逻辑

TextField和TextEditingController绑定后,输入流会自动双向同步,不需要手动在onChanged里给Controller的text属性赋值,删除这段逻辑即可:

class _PasswordInputState extends State<PasswordInput> {
  var hiddenContent = true;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      margin: const EdgeInsets.symmetric(horizontal: 20),
      child: TextField(
        // 完全删除这段冗余的onChanged逻辑
        // onChanged: (value) {
        //   print(value);
        //   widget.password.text = value;
        // },
        textInputAction: TextInputAction.done,
        obscureText: hiddenContent,
        autocorrect: false,
        controller: widget.password,
        keyboardType: TextInputType.text,
        maxLength: 35,
        decoration: InputDecoration(
          prefixIcon: const Icon(Icons.lock),
          suffixIcon: IconButton(
            icon: hiddenContent
                ? const Icon(Icons.visibility)
                : const Icon(Icons.visibility_off),
            onPressed: () {
              setState(() {
                hiddenContent = !hiddenContent;
              });
            },
          ),
          border: const OutlineInputBorder(),
          labelText: 'Password',
        ),
      ),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.01 08:24:06