Flutter+Dart:编辑标签页名称后出现黑屏问题求助
问题:编辑待办清单列表名称后APP黑屏
开发的Dart待办清单APP支持多列表创建、条目增删改排,采用标签页系统管理列表。新增「编辑列表」功能(支持删除列表或修改名称)后,点击「保存」按钮时列表名称修改成功,但APP会出现黑屏,必须重启才能恢复。
相关的「编辑列表」对话框核心代码如下:
// 删除或编辑列表 void confirmDeleteList(String listName) { try { setState(() { db.toDoLists.remove(listName); tabNames.remove(listName); if (_tabController.animation!.status == AnimationStatus.completed) { if (!_isTabControllerDisposed) { _tabController.dispose(); // 销毁旧的TabController _isTabControllerDisposed = true; } _tabController = TabController(length: db.toDoLists.keys.length, vsync: this); _isTabControllerDisposed = false; if (_tabController.length - 1 < _tabController.previousIndex) { _tabController.index = _tabController.length - 2; } else { _tabController.index = _tabController.previousIndex; } _currentList = tabNames[_tabController.index]; // 将当前列表设为当前标签页 } }); db.updateDatabase(); } catch (e) { print('Error in confirmDeleteList: $e'); } } void deleteOrEditList(String listName) { try { showDialog( context: context, builder: (context) { // 为DeleteListBox创建新的TextEditingController final deleteBoxController = TextEditingController(); return DeleteListBox( controller: deleteBoxController, onSaved: () async { if (deleteBoxController.text.isNotEmpty) { setState(() { // 移除旧列表并添加新名称的列表 var items = db.toDoLists.remove(listName); db.toDoLists[deleteBoxController.text] = items!; // 更新tabNames tabNames.remove(listName); tabNames.add(deleteBoxController.text); if (_tabController.animation!.status == AnimationStatus.completed) { if (!_isTabControllerDisposed) { _tabController.dispose(); // 销毁旧的TabController _isTabControllerDisposed = true; } _tabController = TabController(length: db.toDoLists.keys.length, vsync: this); _isTabControllerDisposed = false; _tabController.index = tabNames.indexOf(deleteBoxController.text); // 将重命名后的标签页设为当前页 _currentList = deleteBoxController.text; // 将当前列表设为重命名后的标签页 } }); db.updateDatabase(); deleteBoxController.clear(); } Navigator.of(context).pop(); // 在销毁TabController前关闭对话框 }, onSlided: () { confirmDeleteList(listName); Navigator.of(context).pop(); // 在销毁TabController前关闭对话框 return Future.value(); }, tabName: listName, ); }, ); } catch (e) { print('Error in deleteList: $e'); } }
问题定位
黑屏的核心原因是在setState同步回调中直接销毁并重建TabController,此时Widget树中的TabBar/TabBarView仍持有旧控制器的引用,销毁操作会导致状态不一致,触发渲染错误。此外还有两处细节问题:
_isTabControllerDisposed的状态判断不可靠,容易出现重复销毁或控制器未正确初始化的情况- 重命名列表时,
tabNames采用「移除旧名称+添加新名称」的方式,会打乱原标签页顺序,可能引发索引匹配错误
修复方案
1. 延迟TabController的重建操作
不要在setState内部直接销毁控制器,而是先更新数据状态,再通过WidgetsBinding.instance.addPostFrameCallback在当前帧渲染完成后重建控制器,确保Widget树已完成状态更新:
修改confirmDeleteList方法:
void confirmDeleteList(String listName) { try { setState(() { db.toDoLists.remove(listName); tabNames.remove(listName); // 确保当前列表索引合法 final targetIndex = _tabController.previousIndex.clamp(0, tabNames.length - 1); _currentList = tabNames.isNotEmpty ? tabNames[targetIndex] : ''; }); db.updateDatabase(); // 帧渲染完成后再处理TabController WidgetsBinding.instance.addPostFrameCallback((_) { if (!_isTabControllerDisposed) { _tabController.dispose(); _isTabControllerDisposed = true; } if (tabNames.isNotEmpty) { _tabController = TabController(length: tabNames.length, vsync: this); _isTabControllerDisposed = false; _tabController.index = tabNames.indexOf(_currentList); } }); } catch (e) { print('Error in confirmDeleteList: $e'); } }
2. 重命名时保持标签页顺序(可选优化)
如果希望重命名后标签页位置不变,不要用remove+add修改tabNames,直接修改对应索引的元素:
// 替换原onSaved中的tabNames操作逻辑 final index = tabNames.indexOf(listName); if (index != -1) { tabNames[index] = deleteBoxController.text; }
3. 统一管理TabController的销毁
在Widget的dispose方法中统一销毁控制器,避免重复销毁:
@override void dispose() { if (!_isTabControllerDisposed) { _tabController.dispose(); _isTabControllerDisposed = true; } super.dispose(); }
4. 修改编辑列表的onSaved逻辑
同样将控制器重建移到setState之后的帧回调中:
onSaved: () async { final newName = deleteBoxController.text.trim(); if (newName.isNotEmpty && newName != listName) { setState(() { var items = db.toDoLists.remove(listName); db.toDoLists[newName] = items!; // 保持标签页位置不变的修改方式 final index = tabNames.indexOf(listName); if (index != -1) { tabNames[index] = newName; } _currentList = newName; }); db.updateDatabase(); deleteBoxController.clear(); WidgetsBinding.instance.addPostFrameCallback((_) { if (!_isTabControllerDisposed) { _tabController.dispose(); _isTabControllerDisposed = true; } _tabController = TabController(length: tabNames.length, vsync: this); _isTabControllerDisposed = false; _tabController.index = tabNames.indexOf(_currentList); }); } Navigator.of(context).pop(); },
内容的提问来源于stack exchange,提问作者Santiago
相关产品推荐
相关产品推荐

