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

Flutter中多TextField用不同控制器却输入同步问题求助

问题解决:Flutter注册页面多个TextField内容同步显示

问题描述

开发Flutter注册页面时,已为email、password、name、confirmPassword四个TextField分配独立的TextEditingController,但输入email、password或confirmPassword时,这三个输入框内容会同步显示,仅name输入框正常工作。重新编写控制器代码无效,需解决该问题。

控制器代码

class SignUpController extends GetxController {
  static SignUpController get instance => Get.find();

  final email = TextEditingController();
  final password = TextEditingController();
  final name = TextEditingController();
  final confirmPassword = TextEditingController();

  void registerUser(String email, String password, String name) {
    AuthenticationRepository.instance.createUser(email, password, name);
  }
  @override
  void onClose() {
    email.dispose();
    password.dispose();
    name.dispose();
    confirmPassword.dispose();
    super.onClose();
  }
}

页面State代码

class _AskIfCompanyState extends State<AskIfCompany> {
  final FocusNode _focusNode = FocusNode();
  final _formKey = GlobalKey<FormState>();

  bool isJobSeeker = false;
  bool isCompany = false;

  @override
  void initState() {
    super.initState();
  }

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

  void _handleCheckBoxChange(bool? selected, String type) {
    setState(() {
      if (type == 'jobSeeker') {
        isJobSeeker = selected ?? false;
        if (isJobSeeker) {
          isCompany = false;
        }
      } else if (type == 'company') {
        isCompany = selected ?? false;
        if (isCompany) {
          isJobSeeker = false;
        }
      }
    });
  }

  void _requestFocus() {
    FocusScope.of(context).requestFocus(_focusNode);
  }

  @override
  Widget build(BuildContext context) {
    final controller = Get.put(SignUpController());


    var screenWidth = MediaQuery.of(context).size.width;
    var screenHeight = MediaQuery.of(context).size.height;

    return Scaffold(
      body: SafeArea(
        child: SingleChildScrollView(
          child: Column(
            children: [
              Row(
                children: <Widget>[
                  buildLogo(screenWidth, screenHeight),
                ],
              ),
              const SizedBox(height: 25),
              Row(
                children: [
                  SizedBox(width: screenWidth * 0.15),
                  const Text(
                    'Nom entier:',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 20,
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 5),
              SizedBox(
                child: buildTextField(
                  context: context,
                  controller: controller.name,
                  hinttext: 'Entrez votre nom et prénom',
                ),
              ),
              const SizedBox(height: 5),
              Row(
                children: [
                  SizedBox(width: screenWidth * 0.15),
                  const Text(
                    'Email:',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 20,
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 5),
              SizedBox(
                child: buildTextField(
                  context: context,
                  controller: controller.email,
                  hinttext: 'Entrez votre mail',
                ),
              ),
              const SizedBox(height: 5),
              Row(
                children: [
                  SizedBox(width: screenWidth * 0.15),
                  const Text(
                    'Mot de passe:',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 20,
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 5),
              SizedBox(
                child: buildTextFieldPassword(
                  context: context,
                  controller: controller.password,
                  hinttext: 'Entrez votre mot de passe',
                ),
              ),
              const SizedBox(height: 5),
              Row(
                children: [
                  SizedBox(width: screenWidth * 0.15),
                  const Text(
                    'Confirmez votre mot de passe:',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 20,
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 5),
              SizedBox(
                child: buildTextFieldPassword(
                  controller: controller.confirmPassword,
                  context: context,
                  hinttext: 'Confirmez votre mot de passe',
                ),
              ),
              const SizedBox(height: 15),
              Row(
                children: [
                  SizedBox(width: screenWidth * 0.15),
                  buildRoundCheckBox(
                    isChecked: isJobSeeker,
                    onChanged: (selected) =>
                        _handleCheckBoxChange(selected, 'jobSeeker'),
                    borderColor: const Color(0xffffd5c2),
                    checkedColor: const Color(0xffffd5c2),
                    uncheckedColor: Colors.grey[200] ?? Colors.grey,
                    checkedWidget: const Icon(Icons.check, color: Colors.black),
                    size: 24,
                  ),
                  const SizedBox(width: 10),
                  const Text(
                    'Je suis un candidat',
                    style: TextStyle(fontSize: 15),
                  ),
                ],
              ),
              const SizedBox(height: 20),
              Row(
                children: [
                  SizedBox(width: screenWidth * 0.15),
                  buildRoundCheckBox(
                    isChecked: isCompany,
                    onChanged: (selected) =>
                        _handleCheckBoxChange(selected, 'company'),
                    borderColor: const Color(0xffffd5c2),
                    checkedColor: const Color(0xffffd5c2),
                    uncheckedColor: Colors.grey[200] ?? Colors.grey,
                    checkedWidget: const Icon(Icons.check, color: Colors.black),
                    size: 24,
                  ),
                  const SizedBox(width: 10),
                  const Text(
                    'Je suis une entreprise (HR)',
                    style: TextStyle(fontSize: 15),
                  ),
                ],
              ),
              const SizedBox(height: 20),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  buildElevatedButton(
                    buttonText: 'S\"inscrire',
                    onPressed: () {
                      if (_formKey.currentState!.validate()) {
                        SignUpController.instance.registerUser(
                          controller.email.text.trim(),
                          controller.password.text.trim(),
                          controller.name.text.trim(),
                        );
                      }
                    },
                  ),
                ],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  GestureDetector(
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(builder: (context) => const LogIn()),
                      );
                    },
                    child: const Text(
                      'Déja un compte? Se connecter',
                      style: TextStyle(
                        color: Colors.black,
                        fontSize: 12,
                        fontWeight: FontWeight.w400,
                        decoration: TextDecoration.underline,
                        height: 0,
                      ),
                    ),
                  ),
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

问题原因

控制器定义本身无问题,核心问题出在自定义的buildTextField和buildTextFieldPassword方法实现:

  • 方法未正确使用传入的controller参数,硬编码复用了同一个TextEditingController实例;
  • 或方法内部逻辑错误,导致多个TextField绑定到同一控制器。

另外,在build方法中调用Get.put(SignUpController())虽不会重复创建实例(GetX维护单例),但移至initState更规范,避免不必要调用。

解决方案

1. 修复自定义TextField构建方法

确保方法正确接收并使用传入的controller参数,示例实现如下:

Widget buildTextField({
  required BuildContext context,
  required TextEditingController controller,
  required String hinttext,
}) {
  return Padding(
    padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width * 0.15),
    child: TextField(
      controller: controller, // 必须使用传入的控制器
      decoration: InputDecoration(
        hintText: hinttext,
        border: OutlineInputBorder(),
      ),
    ),
  );
}

Widget buildTextFieldPassword({
  required BuildContext context,
  required TextEditingController controller,
  required String hinttext,
}) {
  return Padding(
    padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width * 0.15),
    child: TextField(
      controller: controller, // 必须使用传入的控制器
      obscureText: true,
      decoration: InputDecoration(
        hintText: hinttext,
        border: OutlineInputBorder(),
      ),
    ),
  );
}

2. 规范控制器初始化位置

将Get.put(SignUpController())从build方法移至initState:

class _AskIfCompanyState extends State<AskIfCompany> {
  final FocusNode _focusNode = FocusNode();
  final _formKey = GlobalKey<FormState>();
  late SignUpController controller;

  bool isJobSeeker = false;
  bool isCompany = false;

  @override
  void initState() {
    super.initState();
    controller = Get.put(SignUpController()); // 在此处初始化控制器
  }

  // 其余代码不变
}

3. 验证控制器唯一性

可在控制器构造函数添加日志,确认是否重复创建:

class SignUpController extends GetxController {
  static SignUpController get instance => Get.find();

  final email = TextEditingController();
  final password = TextEditingController();
  final name = TextEditingController();
  final confirmPassword = TextEditingController();

  SignUpController() {
    print("SignUpController created"); // 添加日志
  }

  // 其余代码不变
}

若日志多次打印,需检查是否在其他位置也调用了Get.put(SignUpController())。

总结

多个TextField内容同步的核心原因是绑定了同一控制器实例,重点排查自定义构建方法的参数使用,同时规范控制器初始化位置即可解决问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 11:59:50