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

Flutter使用Dismissible时报已移除组件仍在树中 如何保留其余输入数据

问题根源

你遇到的报错和输入内容丢失的问题,核心是两个原因:

  1. 你当前使用ValueKey(_count[index])作为Dismissible的Key,当你删除中间位置的条目后,后面所有条目的索引都会前移,Key对应的_count[index]值会发生变化,Flutter的组件Diff算法无法正确识别哪个条目被移除,就会抛出a dismissed dismissible widget is still part of the tree异常。
  2. 使用UniqueKey()会导致每次setState时所有Dismissible组件的Key都被重置,所有输入框组件会强制重建,存储在组件局部状态里的输入内容自然会丢失。

解决方案

1. 调整数据结构,给每个训练组设置固定唯一标识

不要用纯数字数组存储计数,而是定义专门的训练组数据类,每个训练组自带一个生成后就不会变化的唯一ID,同时把重量、次数的输入内容直接存在对应的数据对象里,不要依赖输入框的局部状态存储数据。

// 定义训练组数据类
class TrainingSet {
  // 唯一ID,生成后固定不变,用作Dismissible的Key
  final String id;
  // 存储用户输入的重量、次数
  double weight;
  int reps;

  TrainingSet({
    required this.id,
    required this.weight,
    required this.reps,
  });
}

2. 调整状态存储逻辑

将原来的_count数组替换为TrainingSet类型的数组,初始化和新增训练组时,用时间戳生成唯一ID:

late List<TrainingSet> _trainingSets;

@override
void initState() {
  super.initState();
  // 初始化默认添加第一组训练
  _trainingSets = [
    TrainingSet(
      // 用微秒时间戳生成唯一ID,不会重复
      id: DateTime.now().microsecondsSinceEpoch.toString(),
      weight: 0,
      reps: 0,
    )
  ];
}

3. 修改Dismissible的Key绑定和输入框逻辑

将Dismissible的Key绑定到每个训练组的固定ID,同时输入框的内容直接读取对应TrainingSet对象的属性,输入变化时直接更新对应对象的数值:

ListView.builder(
  shrinkWrap: true,
  itemCount: _trainingSets.length,
  itemBuilder: (context, index) {
    final set = _trainingSets[index];
    int setNumber = index + 1;
    return Dismissible(
      // 绑定固定唯一ID,不会随其他条目删除变化
      key: ValueKey(set.id),
      background: _swipeStyle(),
      onDismissed: (direction) {
        setState(() {
          _trainingSets.removeAt(index);
        });
      },
      child: Row(
        children: [
          Expanded(flex: 1, child: Text('Set $setNumber')),
          Expanded(
            flex: 2, 
            // 输入框直接绑定set.weight,输入时更新set.weight的值
            child: TextField(
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(hintText: '重量'),
              onChanged: (value) {
                setState(() {
                  set.weight = double.tryParse(value) ?? 0;
                });
              },
              controller: TextEditingController(text: set.weight == 0 ? '' : set.weight.toString()),
            )
          ),
          const SizedBox(width: 24.0),
          Expanded(
            flex: 2, 
            // 次数输入框同理绑定set.reps
            child: TextField(
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(hintText: '次数'),
              onChanged: (value) {
                setState(() {
                  set.reps = int.tryParse(value) ?? 0;
                });
              },
              controller: TextEditingController(text: set.reps == 0 ? '' : set.reps.toString()),
            )
          ),
        ],
      ),
    );
  },
)

这个方案下,每个训练组的Key固定唯一,删除条目时Flutter能正确识别被移除的组件不会报错,同时所有输入内容都存在对应的数据对象中,其他条目的输入内容不会受删除操作影响。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 22:15:02