SingleChildScrollView嵌套两个ListView.builder引发应用冻结问题
问题原因
应用冻结的直接诱因是两个ListView.builder均未设置itemCount参数。ListView.builder默认采用懒加载机制,未指定itemCount时会判定列表项数量为无限大;同时你给列表设置了shrinkWrap: true,该属性要求列表一次性计算所有子项的总高度来确定自身布局尺寸,布局阶段会陷入无限计算子项的死循环,直接阻塞UI线程,最终表现为页面打开后无响应、应用冻结。
修复方案
最小改动修复
只需要给两个ListView.builder补充明确的itemCount参数,传入每个列表实际需要渲染的条目总数即可解决死循环问题,示例代码如下:
body: SingleChildScrollView( physics: const ScrollPhysics(), child: Column( children: <Widget>[ ListView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: firstList.length, // 替换为第一个列表的实际数据长度 itemBuilder: (context, index) { return Container( padding: const EdgeInsets.all(10), height: 75, child: Row( children: <Widget>[ // 补充第一个列表的项内容 ], ), ); } ), ListView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: secondList.length, // 替换为第二个列表的实际数据长度 itemBuilder: (context, index) { return Container( padding: const EdgeInsets.all(10), height: 75, child: Row( children: <Widget>[ // 补充第二个列表的项内容 ], ), ); } ), ], ), ),
更优性能实现
上述最小修复虽然能解决冻结问题,但shrinkWrap: true会让列表一次性渲染所有子项,完全失去懒加载能力,当列表数据量较大时会出现卡顿、内存占用高的问题。如果要实现同一个滚动域下的多列表效果,更推荐使用CustomScrollView配合Sliver组件实现,无需禁用子列表滚动、无需设置shrinkWrap,原生支持懒加载,性能更好:
body: CustomScrollView( slivers: <Widget>[ // 第一个分组列表 SliverList( delegate: SliverChildBuilderDelegate( (context, index) { return Container( padding: const EdgeInsets.all(10), height: 75, child: Row( children: <Widget>[ // 第一个列表的项内容 ], ), ); }, childCount: firstList.length, // 第一个列表条目总数 ), ), // 第二个分组列表 SliverList( delegate: SliverChildBuilderDelegate( (context, index) { return Container( padding: const EdgeInsets.all(10), height: 75, child: Row( children: <Widget>[ // 第二个列表的项内容 ], ), ); }, childCount: secondList.length, // 第二个列表条目总数 ), ), ], )
内容的提问来源于stack exchange,提问作者user11152343
相关产品推荐
相关产品推荐

