You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 09:45:03