Flutter:如何监听ListView.Builder滚动结束的最后一帧事件
解决ListView滚动结束后再更新列表项文本的问题
问题描述
现有一个可创建10000行的ListView.Builder,每行包含图标和显示“index - N”的容器。需求为:在滑动或滚动操作时,仅在滚动结束的最后一帧(ListView.Builder的最后一次屏幕更新)更新容器中的文本,而非滚动过程中实时更新。
解决方案
要实现该需求,需结合NotificationListener监听滚动状态,将组件改为StatefulWidget维护滚动状态,并控制文本更新时机:
- 将原
StatelessWidget改为StatefulWidget,添加滚动控制器和状态变量。 - 用
NotificationListener包裹ListView,监听滚动开始/结束事件,更新滚动状态。 - 滚动过程中使用缓存的文本内容,滚动结束后更新可见项的文本并触发组件重建。
修改后的完整代码
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: ListViewBuilder(), ); } } class ListViewBuilder extends StatefulWidget { const ListViewBuilder({super.key}); @override State<ListViewBuilder> createState() => _ListViewBuilderState(); } class _ListViewBuilderState extends State<ListViewBuilder> { final ScrollController _scrollController = ScrollController(); bool _isScrolling = false; // 缓存列表项文本,滚动过程中使用缓存值 final Map<int, String> _cachedTexts = {}; @override void initState() { super.initState(); // 初始化可见区域的文本缓存,避免初始显示占位符 WidgetsBinding.instance.addPostFrameCallback((_) { _updateVisibleItemTexts(); }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } // 更新可见区域的文本缓存 void _updateVisibleItemTexts() { if (!mounted) return; final renderObject = context.findRenderObject() as RenderBox?; if (renderObject == null) return; final viewport = RenderAbstractViewport.of(renderObject); final offset = _scrollController.offset; final visibleStart = viewport.getOffsetToReveal(renderObject, 0.0).offset; final visibleEnd = viewport.getOffsetToReveal(renderObject, 1.0).offset; final itemExtent = _scrollController.position.itemExtent; final startIndex = ((offset - visibleStart) / itemExtent).floor().clamp(0, 9999); final endIndex = ((offset - visibleEnd) / itemExtent).ceil().clamp(0, 9999); for (int i = startIndex; i <= endIndex; i++) { _cachedTexts[i] = ' index - $i'; } setState(() {}); } @override Widget build(BuildContext context) { return Scaffold( body: NotificationListener<ScrollNotification>( onNotification: (notification) { if (notification is ScrollStartNotification) { setState(() { _isScrolling = true; }); } else if (notification is ScrollEndNotification) { setState(() { _isScrolling = false; }); // 滚动结束后更新可见项的文本缓存 _updateVisibleItemTexts(); } return false; }, child: ListView.builder( controller: _scrollController, itemCount: 10000, itemBuilder: (BuildContext context, int index) { return Row( children: [ const Icon(Icons.list), SizedBox( height: 30, width: 200, // 滚动时显示缓存文本,滚动结束后显示最新文本 child: Text(_isScrolling ? _cachedTexts[index] ?? ' index - ...' : ' index - $index'), ), ], ); }, ), ), ); } }
代码说明
_isScrolling:标记当前是否处于滚动状态,控制文本显示逻辑。_cachedTexts:缓存每个列表项的文本内容,滚动过程中保持文本不变。_updateVisibleItemTexts:滚动结束后计算可见区域的列表项索引,更新对应缓存文本并触发组件重建。NotificationListener:监听ScrollStartNotification和ScrollEndNotification事件,更新滚动状态并触发缓存更新。
这样就能实现需求:滚动过程中列表项文本保持原有内容,仅在滚动结束的最后一帧更新为当前索引对应的文本。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

