如何避免ListView配合StreamBuilder使用时出现的不必要刷新?支持GetX方案
问题根源
你的代码中_chosenData是页面级的状态变量,调用setState会触发整个Schedule页面重绘,左侧的StreamBuilder和ListView也会跟着重建,哪怕Firebase数据流没有新数据也会走完整渲染逻辑,产生不必要的性能消耗。
方案1:原生Flutter实现(无第三方依赖)
使用ValueNotifier+ValueListenableBuilder单独管理选中数据状态,仅刷新右侧展示区域:
class Schedule extends StatefulWidget { @override _ScheduleState createState() => _ScheduleState(); } class _ScheduleState extends State<Schedule> { final FirebaseFirestore _db = FirebaseFirestore.instance; final DateTime _yesterday = DateTime.now().subtract(const Duration(days: 1)); // 用ValueNotifier单独封装选中数据,脱离页面级状态管理 final ValueNotifier<QueryDocumentSnapshot?> _chosenData = ValueNotifier(null); @override void dispose() { _chosenData.dispose(); // 销毁时释放资源,避免内存泄漏 super.dispose(); } @override Widget build(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: StreamBuilder<QuerySnapshot>( stream: _db.collection('Schedule').where('date', isGreaterThan: _yesterday).limit(10).orderBy('date').snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.active) { return ListView.builder( itemCount: snapshot.data!.docs.length, itemBuilder: (context, index) { var data = snapshot.data!.docs[index]; return ListTile( leading: const Icon(Icons.person), title: Text(data['project'], style: const TextStyle(fontWeight: FontWeight.bold)), subtitle: Text(data['parkour']), onTap: () { // 直接更新ValueNotifier的值,不调用setState _chosenData.value = data; }, ); }, ); } else { return const Center(child: CupertinoActivityIndicator()); } }, ), ), const VerticalDivider(), Expanded( // 仅监听选中数据变化,单独刷新当前区域 child: ValueListenableBuilder<QueryDocumentSnapshot?>( valueListenable: _chosenData, builder: (context, value, child) { return Container( alignment: Alignment.center, color: Colors.black26, // 若要展示具体数据而非实例类型,可改成 Text(value?.data().toString() ?? '未选中数据') child: Text('$value'), ); }, ), ), ], ); } }
方案2:GetX状态管理实现
引入GetX依赖后,用响应式变量管理选中状态,代码更简洁:
// 无需StatefulWidget,直接用StatelessWidget即可 class Schedule extends StatelessWidget { Schedule({super.key}); final FirebaseFirestore _db = FirebaseFirestore.instance; final DateTime _yesterday = DateTime.now().subtract(const Duration(days: 1)); // 定义响应式变量 final _chosenData = Rxn<QueryDocumentSnapshot>(); @override Widget build(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: StreamBuilder<QuerySnapshot>( stream: _db.collection('Schedule').where('date', isGreaterThan: _yesterday).limit(10).orderBy('date').snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.active) { return ListView.builder( itemCount: snapshot.data!.docs.length, itemBuilder: (context, index) { var data = snapshot.data!.docs[index]; return ListTile( leading: const Icon(Icons.person), title: Text(data['project'], style: const TextStyle(fontWeight: FontWeight.bold)), subtitle: Text(data['parkour']), onTap: () { // 直接更新响应式变量 _chosenData.value = data; }, ); }, ); } else { return const Center(child: CupertinoActivityIndicator()); } }, ), ), const VerticalDivider(), Expanded( // Obx仅监听内部用到的响应式变量变化,单独刷新当前区域 child: Obx(() => Container( alignment: Alignment.center, color: Colors.black26, child: Text('${_chosenData.value}'), )), ), ], ); } }
以上两种方案都可以保证点击列表项时,仅右侧展示区域刷新,左侧ListView不会触发不必要的重绘。
内容的提问来源于stack exchange,提问作者SLendeR
相关产品推荐
相关产品推荐

