Flutter:带状态的ListView.Builder滚动时如何监听最后一帧事件
解决方案
要实现滚动过程中仅在最后一帧更新列表项文本的需求,可以通过ScrollController结合SchedulerBinding来监听滚动状态并控制更新时机,具体实现如下:
核心思路
- 使用
ScrollController监听列表滚动状态,判断是否处于滚动过程中。 - 借助
SchedulerBinding.addPostFrameCallback,确保仅在当前渲染帧的最后阶段执行文本更新,避免滚动过程中频繁刷新UI。 - 维护状态变量存储显示文本,仅在合适时机触发
setState更新。
完整代码实现
import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final ScrollController _scrollController = ScrollController(); // 存储每个列表项的显示文本,初始化为默认值 final List<String> _itemTexts = List.generate(1000, (index) => 'index - $index'); // 标记是否需要在帧结束时更新文本 bool _needUpdate = false; @override void initState() { super.initState(); // 添加滚动监听 _scrollController.addListener(_onScroll); } void _onScroll() { // 当列表正在滚动时,标记需要更新 if (_scrollController.position.isScrollingNotifier.value) { _needUpdate = true; // 注册帧结束回调,确保仅在最后一帧执行更新 SchedulerBinding.instance.addPostFrameCallback((_) { if (_needUpdate) { setState(() { // 模拟更新逻辑,可根据实际需求调整 for (int i = 0; i < 1000; i++) { _itemTexts[i] = 'index - $i (updated)'; } _needUpdate = false; }); } }); } } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: ListView.builder( controller: _scrollController, itemCount: 1000, itemBuilder: (BuildContext context, int index) { return Row( children: [ const Icon(Icons.list), SizedBox( height: 30, child: Text(' ${_itemTexts[index]}'), ), ], ); }, ), ); } }
关键细节说明
- ScrollController监听:通过
isScrollingNotifier.value判断列表是否处于滚动状态,避免非滚动场景触发更新。 - SchedulerBinding帧回调:
addPostFrameCallback会在当前帧所有Widget构建完成后执行,保证这是当前滚动周期的最后一帧更新。 - 状态控制:使用
_needUpdate标记避免重复注册回调,减少不必要的UI刷新。
如果需要在滚动完全停止后再更新文本,可以替换为监听ScrollEndNotification的方式:
// 替换原ListView部分为以下代码 body: NotificationListener<ScrollEndNotification>( onNotification: (notification) { SchedulerBinding.instance.addPostFrameCallback((_) { setState(() { // 执行文本更新逻辑 for (int i = 0; i < 1000; i++) { _itemTexts[i] = 'index - $i (updated)'; } }); }); return true; }, child: ListView.builder( itemCount: 1000, itemBuilder: (BuildContext context, int index) { return Row( children: [ const Icon(Icons.list), SizedBox( height: 30, child: Text(' ${_itemTexts[index]}'), ), ], ); }, ), )
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

