Flutter如何正确在FutureBuilder中使用setState避免列表闪烁
核心问题根因
当前代码出现点击复选框列表闪烁的本质原因是:
FutureBuilder的future参数是在build方法内部直接调用异步方法生成的,只要执行一次setState触发页面整体重绘,就会生成一个全新的Future实例。FutureBuilder内部会对比新旧Future的引用,只要引用不一致,就会重置异步状态、切回加载视图,哪怕列表的数据源根本没有发生任何变化,也会导致整个ListView被销毁重建。- 复选框的勾选状态更新触发的顶层
setState,会连带跑完整的页面build流程,刚好命中上面的Future重复创建问题,最终表现就是每点一次复选框,整个列表就因为重新加载闪一次。
可落地的修复方案
1. 抽离Future实例,打破“每次build都新建异步任务”的循环
在State类的成员变量区域,提前存储创建好的异步任务实例,避免在build流程中直接生成新的Future:
// 存储已创建的列表异步任务 late Future<List<String>> _aptNameListFuture; // 记录上次创建Future时的关键参数,用来判断是否真的需要重新拉取列表数据 ArgumentClass? _lastFutureArgs; bool? _lastIsLastDepth;
抽离统一的判断逻辑,只有当真正影响列表内容的状态变化时,才重新生成Future实例(也就是下拉联动参数改变、是否为最后一级深度的判断状态_bLastDepth改变时):
void _syncAptListFuture() { // 对比当前和上次的状态,只有发生实质变化才重新创建Future final argsChanged = widget.ReceiveArgs != _lastFutureArgs; final depthStateChanged = _bLastDepth != _lastIsLastDepth; if (argsChanged || depthStateChanged) { _lastFutureArgs = widget.ReceiveArgs; _lastIsLastDepth = _bLastDepth; // 仅在需要刷新列表时才调用异步方法 _aptNameListFuture = AptNameListView(widget.ReceiveArgs); } }
把这个方法的调用时机固定在两个场景:
- 页面初始化的
initState中,第一次初始化列表的Future - 下拉选项变更的
setState内部,更新完下拉联动参数、深度判断状态之后调用
2. 修改FutureBuilder的入参,传入稳定的Future引用
调整原来的AptListview实现,把直接在参数里新建Future的代码,替换成已经存好的稳定实例,从根源上避免FutureBuilder重复执行异步逻辑:
Widget AptListview() { // 每次build前先判断是否需要同步最新的Future _syncAptListFuture(); return FutureBuilder<List<String>>( // 传入稳定的Future实例,引用不变时FutureBuilder不会重新执行异步 future: _aptNameListFuture, builder: (context, snapshot) { if (_bLastDepth == false) { return const Text("Select Address"); } // 只有真的在等待异步结果时才显示加载圈 if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } // 处理异常/空数据场景 if (snapshot.hasError || snapshot.data?.isEmpty == true) { return const Text("暂无匹配地址"); } return Expanded( child: ListView.builder( shrinkWrap: true, itemCount: _AptNameList.length, itemBuilder: (context, index) { return CheckboxListTile( // 给列表项加稳定key,帮助框架复用元素,减少重绘 key: ValueKey('apt_item_$index'), title: Text(_AptNameList[index]), value: _isAptChecked[index], onChanged: (value) { setState(() { _isAptChecked[index] = value!; }); }, ); }, ), ); }, ); }
这一步完成后,哪怕点击复选框触发顶层页面的重绘,因为传入FutureBuilder的Future引用保持稳定,FutureBuilder就不会重置加载状态,ListView的元素会被Flutter框架自动复用,只会更新对应复选框的选中状态,不会出现整列表闪烁的问题。
3. 可选性能优化:局部化复选框状态更新
如果后续需要进一步降低重绘范围,可以把单个列表项抽成独立的StatefulWidget,把复选框的选中状态存在组件内部,点击时只在组件内部调用setState更新,不需要触发整个页面的顶层重绘,进一步提升列表的流畅度。
内容的提问来源于stack exchange,提问作者chan hong Park
相关产品推荐
相关产品推荐

