Flutter中不使用shrinkWrap提升嵌套ListView性能的方法
优化Flutter分类展开列表:移除shrinkWrap保留懒加载性能
你的当前实现通过嵌套ListView.builder并设置shrinkWrap: true实现分类展开效果,但shrinkWrap会强制列表一次性计算并渲染所有子项,完全丧失builder懒加载的性能优势,当商品数量较多时会明显卡顿。下面提供两种无需shrinkWrap的优化方案:
方案一:CustomScrollView + Sliver 组合(自定义滚动视图)
将分类标题和展开的商品列表拆分为独立的Sliver组件,统一放在CustomScrollView中,利用Sliver的懒加载特性保持性能,同时满足"展开时下推下方分类"的交互需求。
class CategoryPage extends StatefulWidget { const CategoryPage({Key? key}) : super(key: key); @override State<CategoryPage> createState() => _CategoryPageState(); } class _CategoryPageState extends State<CategoryPage> { Category? selectedCategory; final List<Category> categories = [ Category(name: 'Breakfast', products: ['Cake', 'Sandwich']), Category(name: 'Lunch', products: ['Chicken', 'Pizza']), ]; Widget _buildCategoryHeader(Category category) { return GestureDetector( onTap: () { setState(() { // 点击同一分类时切换展开/收起状态 selectedCategory = selectedCategory == category ? null : category; }); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 10), margin: const EdgeInsets.only(top: 10), alignment: Alignment.center, width: double.infinity, decoration: BoxDecoration( color: selectedCategory == category ? Colors.green : Colors.grey, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text(category.name), Icon( selectedCategory == category ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, ), ], ), ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('优化后的分类页')), body: CustomScrollView( slivers: categories.expand((category) { final List<Sliver> slivers = [ SliverToBoxAdapter(child: _buildCategoryHeader(category)), ]; // 展开时添加商品列表的Sliver if (selectedCategory == category) { slivers.add(SliverList( delegate: SliverChildBuilderDelegate( (context, index) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), child: Text(category.products[index]), ); }, childCount: category.products.length, ), )); } return slivers; }).toList(), ), ); } } class Category { final String name; final List<String> products; Category({required this.name, required this.products}); }
优势
- 所有内容在同一个滚动视图中,避免嵌套滚动的性能问题
SliverList仅渲染可见区域的商品项,完全保留懒加载优势- 展开/收起时自动调整布局,下方分类自然下推
方案二:使用官方ExpansionPanelList组件
Flutter官方提供的ExpansionPanelList专门用于实现展开/收起的列表场景,内部已做性能优化,无需手动处理嵌套列表,代码更简洁。
class CategoryPage extends StatefulWidget { const CategoryPage({Key? key}) : super(key: key); @override State<CategoryPage> createState() => _CategoryPageState(); } class _CategoryPageState extends State<CategoryPage> { Category? selectedCategory; final List<Category> categories = [ Category(name: 'Breakfast', products: ['Cake', 'Sandwich']), Category(name: 'Lunch', products: ['Chicken', 'Pizza']), ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('ExpansionPanel实现')), body: ListView( children: [ ExpansionPanelList.radio( elevation: 1, expandedHeaderPadding: EdgeInsets.zero, children: categories.map((category) { return ExpansionPanelRadio( value: category, headerBuilder: (context, isExpanded) { return Container( padding: const EdgeInsets.symmetric(vertical: 10), alignment: Alignment.center, width: double.infinity, color: isExpanded ? Colors.green : Colors.grey, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text(category.name), Icon( isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, ), ], ), ); }, body: Column( children: category.products.map((product) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), child: Text(product), ); }).toList(), ), ); }).toList(), initialOpenPanelValue: selectedCategory, onChanged: (value) { setState(() { selectedCategory = value; }); }, ), ], ), ); } } class Category { final String name; final List<String> products; Category({required this.name, required this.products}); }
优势
- 官方组件,自带单选展开/收起逻辑,无需手动维护选中状态的切换
- 内部优化了渲染逻辑,仅渲染可见的展开内容
- 代码量更少,样式和交互可通过参数快速调整
内容的提问来源于stack exchange,提问作者Abdi mussa
相关产品推荐
相关产品推荐

