Flutter使用Dismissible时报已移除组件仍在树中 如何保留其余输入数据
问题根源
你遇到的报错和输入内容丢失的问题,核心是两个原因:
- 你当前使用
ValueKey(_count[index])作为Dismissible的Key,当你删除中间位置的条目后,后面所有条目的索引都会前移,Key对应的_count[index]值会发生变化,Flutter的组件Diff算法无法正确识别哪个条目被移除,就会抛出a dismissed dismissible widget is still part of the tree异常。 - 使用
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
相关产品推荐
相关产品推荐

