使用ListView.builder实现单ExpansionTile展开时遇_state != null断言错误
解决ListView.builder中单一展开ExpansionTile的断言错误问题
问题根源
你遇到的Error: Assertion failed: _state != null错误,是因为expansionControllers在build方法内生成,每次setState都会重建控制器实例,导致新控制器与ExpansionTile的内部状态解绑,出现_state为空的情况。同时当前代码未处理收起之前展开tile的逻辑,无法实现单一展开的需求。
修复方案
将控制器和跟踪变量提升为State类成员
把expansionControllers和prevIndexExpanded移到State类的成员变量中,在initState里初始化控制器,确保实例稳定,不会随build重建。在展开时自动收起之前的tile
在onExpansionChanged回调中,先判断是否有已展开的tile,调用对应控制器的collapse()方法收起,再更新当前展开的索引。
完整代码示例
class SingleExpansionList extends StatefulWidget { final List<YourDataModel> data; // 替换成你的数据模型类 const SingleExpansionList({super.key, required this.data}); @override State<SingleExpansionList> createState() => _SingleExpansionListState(); } class _SingleExpansionListState extends State<SingleExpansionList> { late List<ExpansionTileController> expansionControllers; int? prevIndexExpanded; @override void initState() { super.initState(); // 在initState中初始化控制器,保证实例唯一 expansionControllers = List.generate( widget.data.length, (index) => ExpansionTileController(), ); } @override Widget build(BuildContext context) { return ListView.builder( itemCount: widget.data.length, itemBuilder: (context, index) { return ExpansionTile( controller: expansionControllers[index], leading: Text(widget.data[index].code), title: Text(widget.data[index].name), onExpansionChanged: (value) async { if (value) { // 收起之前展开的tile(如果存在且不是当前索引) if (prevIndexExpanded != null && prevIndexExpanded != index) { await expansionControllers[prevIndexExpanded!].collapse(); } setState(() { prevIndexExpanded = index; }); // 这里执行你的其他业务逻辑 } else { // 收起当前tile时,重置跟踪索引 setState(() { prevIndexExpanded = null; }); } }, children: [ // 你的子组件内容示例 Padding( padding: const EdgeInsets.all(16.0), child: Text(widget.data[index].detail), ), ], ); }, ); } }
关键说明
- 控制器必须在
initState初始化,避免每次build重建导致的状态不匹配问题。 - 使用
await等待collapse()完成,确保状态更新后再设置新的展开索引,避免UI异常。 - 处理收起当前tile的情况,重置
prevIndexExpanded,保证逻辑完整性。
内容的提问来源于stack exchange,提问作者Mihail Iliev
相关产品推荐
相关产品推荐

