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

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仍持有旧控制器的引用,销毁操作会导致状态不一致,触发渲染错误。此外还有两处细节问题:

  1. _isTabControllerDisposed的状态判断不可靠,容易出现重复销毁或控制器未正确初始化的情况
  2. 重命名列表时,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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 16:34:53