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

Flutter开发Todo应用时新建待办标题描述重复该如何解决

问题根因

你代码的核心问题有3个:

  • 所有列表项的输入框共用了全局的2个TextEditingController,控制器内容变化时所有绑定的输入框会同步更新,自然所有待办内容完全一致
  • todos数组仅存储空字符串,没有为每个待办单独设计存储标题、描述的数据结构
  • Dismissible的Key值重复,后续做删除操作时会出现逻辑异常

修复方案

首先先定义Todo实体类,用来存储单个待办的独立数据,可直接写在HomePage类外面:

class Todo {
  final String id;
  String title;
  String detail;
  Todo({
    required this.id,
    this.title = '',
    this.detail = '',
  });
}

修改State内的代码,完整修改后如下:

class _HomePageState extends State<HomePage> {
  // 替换原有的todos数组,类型改为Todo列表
  List<Todo> todos = [];

  @override
  void initState() {
    super.initState();
    // 初始新增一个空Todo,用毫秒时间戳做唯一id
    todos.add(Todo(id: DateTime.now().millisecondsSinceEpoch.toString()));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          width: double.infinity,
          padding: const EdgeInsets.symmetric(
            horizontal: 24.0,
          ),
          color: const Color(0xfff6f6f6f6),
          child: Stack(
            children: [
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                    margin: const EdgeInsets.only(bottom: 32.0, top: 32.0),
                    child: const Text(
                      "Reminders",
                      style: TextStyle(
                          fontSize: 25.0,
                          fontWeight: FontWeight.bold
                      ),
                    ),
                  ),
                  Expanded(
                    child: ListView.builder(
                      itemCount: todos.length,
                      physics: const BouncingScrollPhysics(),
                      itemBuilder: (BuildContext context, int index) {
                        final currentTodo = todos[index];
                        return Dismissible(
                          // 用Todo的唯一id做Key,避免重复
                          key: Key(currentTodo.id),
                          // 补充删除逻辑
                          onDismissed: (direction) {
                            setState(() {
                              todos.removeAt(index);
                            });
                          },
                          child: Container(
                            width: double.infinity,
                            padding: const EdgeInsets.symmetric(
                                vertical: 20.0,
                                horizontal: 24.0
                            ),
                            margin: const EdgeInsets.only(
                              bottom: 20.0,
                            ),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                TextField(
                                  cursorColor: Colors.black,
                                  autofocus: index == todos.length -1, // 只有新增的待办自动聚焦
                                  // 移除全局controller,用onChanged实时更新当前Todo的标题
                                  onChanged: (value) {
                                    setState(() {
                                      currentTodo.title = value;
                                    });
                                  },
                                  // 绑定当前Todo的标题为初始值
                                  controller: TextEditingController(text: currentTodo.title),
                                  style: const TextStyle(
                                      fontSize: 22.0,
                                      fontWeight: FontWeight.bold
                                  ),
                                  decoration: const InputDecoration(
                                    hintText: "Enter a title",
                                    border: InputBorder.none,
                                  ),
                                ),
                                const Divider(
                                  color: Colors.black,
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(top: 0.0),
                                  child: TextField(
                                    // 移除全局controller,用onChanged实时更新当前Todo的描述
                                    onChanged: (value) {
                                      setState(() {
                                        currentTodo.detail = value;
                                      });
                                    },
                                    // 绑定当前Todo的描述为初始值
                                    controller: TextEditingController(text: currentTodo.detail),
                                    style: TextStyle(
                                      fontSize: 20.0,
                                      color: Colors.grey[900],
                                    ),
                                    cursorColor: Colors.black,
                                    maxLines: null,
                                    decoration: const InputDecoration(
                                        hintText: "Enter the description",
                                        label: Text("description"),
                                        border: InputBorder.none
                                    ),
                                  ),
                                ),
                              ],
                            ),
                            decoration: BoxDecoration(
                                color: Colors.white,
                                borderRadius: BorderRadius.circular(20.0)
                            ),
                          ),
                        );
                      },
                    ),
                  )
                ],
              ),
              Positioned(
                bottom: 24.0,
                right: 0.0,
                child: GestureDetector(
                  onTap: () {
                    setState(() {
                      // 新增时创建新的Todo实例,和已有待办完全独立
                      todos.add(Todo(id: DateTime.now().millisecondsSinceEpoch.toString()));
                    });
                  },
                  child: Container(
                    width: 60.0,
                    height: 60.0,
                    decoration: BoxDecoration(
                      color: Colors.black87,
                      borderRadius: BorderRadius.circular(20.0),
                    ),
                    child: const Icon(Icons.add, color: Colors.white, size: 35.0),
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

核心修改点说明

  • 每个待办都有独立的Todo实例存储标题和描述,数据完全隔离不会互相干扰
  • 移除了全局输入控制器,每个输入框单独绑定对应待办的数据,修改只会作用到当前待办
  • 优化了自动聚焦逻辑,只有新增的最后一条待办会自动触发聚焦,符合用户操作习惯
  • 补充了删除待办的逻辑,滑动删除可以正常生效

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 11:27:03