使用分组数据构建ExpansionTile时遇严重性能问题求优化方案
分组ExpansionTile性能优化方案
核心问题分析
你的代码存在几个关键性能瓶颈:
- Map的
keys.elementAt/values.elementAt是O(n)遍历操作,分组数量越多开销越大 - 收起状态下仍会创建所有子ListTile,造成不必要的Widget实例化
- 字符串格式化、正则判断等计算在每次build时重复执行
- 没有对分组结果做缓存,可能导致重复分组计算
具体优化步骤
1. 预转换分组数据为List,消除O(n)查找
将groupBy返回的Map转换为List条目,让索引访问变为O(1)操作:
// 预转换分组数据,避免每次elementAt的遍历开销 final groupedList = groupedData.entries .map((entry) => _GroupEntry(entry.key, entry.value)) .toList(); // 定义简单的数据类存储分组信息 class _GroupEntry { final String groupName; final List<ClientsToContactData> clients; _GroupEntry(this.groupName, this.clients); } // 构建列表时直接通过索引获取分组 return ListView.builder( itemCount: groupedList.length, itemBuilder: (context, groupIndex) { final group = groupedList[groupIndex]; return ExpansionTile( backgroundColor: Colors.blueAccent.withOpacity(0.1), title: Text(group.groupName), children: [_buildClientList(group.clients)], ); }, );
2. 子列表延迟加载,仅在展开时按需构建
用ListView.builder作为ExpansionTile的子Widget,设置shrinkWrap和禁止独立滚动,确保只有展开时才创建子项:
Widget _buildClientList(List<ClientsToContactData> clients) { return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: clients.length, itemBuilder: (context, index) { final client = clients[index]; return ListTile( minVerticalPadding: 20, title: Text(client.cliente), leading: const Icon(Icons.person_rounded), trailing: Text( client.flagMoraDisplay, style: Theme.of(context).textTheme.titleSmall, ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(client.formattedSaldo), Text(client.convenio), ], ), ); }, ); }
3. 缓存计算结果,避免重复字符串处理
给数据模型添加扩展方法,缓存格式化后的字符串:
extension ClientsToContactDataExt on ClientsToContactData { String get formattedSaldo { final raw = saldoVencido.toStringAsFixed(2); return '\$ ${raw.replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (m) => '${m[1]},', )}'; } String get flagMoraDisplay { return flagMora.contains(RegExp(r'\d')) ? '$flagMora G' : flagMora; } }
4. 缓存分组结果,避免重复计算
将分组逻辑缓存,确保只有数据源变化时才重新分组:
// 用memoized缓存分组结果 final groupedData = _memoizedGroupData(state.clientsToContact!.data!); Map<String, List<ClientsToContactData>> _memoizedGroupData(List<ClientsToContactData> data) { static List<ClientsToContactData>? _lastData; static Map<String, List<ClientsToContactData>>? _cachedResult; if (_lastData != data) { _lastData = data; _cachedResult = groupBy(data, (e) => e.flagMora); } return _cachedResult!; }
5. 减少Widget重建
- 对静态Widget使用
const构造(如const Icon(Icons.person_rounded)) - 如果整个列表频繁重建,考虑用
ValueListenableBuilder或Consumer仅更新数据源变化的部分 - 若子项滚动后容易销毁,可给ListTile所在的Widget添加
AutomaticKeepAliveClientMixin
内容的提问来源于stack exchange,提问作者Samuel Alcántara
相关产品推荐
相关产品推荐

