Flutter实现上下两个ListView Builder按索引同步滚动问询
实现不同高度Item的ListView按索引同步滚动
因为两个ListView的Item高度不一致,直接同步滚动偏移量会导致索引错位,核心思路是跟踪当前列表的可见索引,计算另一个列表对应索引的滚动偏移量并跳转,具体实现步骤如下:
1. 维护Item高度记录列表
在_WeatherCityitemState中添加两个列表,用于存储每个Item的高度:
List<double> _topItemHeights = []; List<double> _bottomItemHeights = []; ScrollController _controllerTop = ScrollController(); ScrollController _controllerBottom = ScrollController(); ScrollingList scrollingList = ScrollingList.none;
2. 初始化高度列表
在initState中初始化高度列表,同时监听字体大小变化(如果有动态调整字体的需求):
@override void initState() { super.initState(); _controllerTop = ScrollController(); _controllerBottom = ScrollController(); // 初始化高度列表,长度与列表Item数一致 _topItemHeights = List.filled(widget.verseTamil.length, 0.0); _bottomItemHeights = List.filled(widget.verseEnglish.length, 0.0); // 监听字体大小变化,重置高度记录 ever(Get.find<StateController>().fontSize, (newSize) { setState(() { _topItemHeights = List.filled(widget.verseTamil.length, 0.0); _bottomItemHeights = List.filled(widget.verseEnglish.length, 0.0); }); }); }
3. 记录每个Item的高度
在两个ListView的itemBuilder中,用LayoutBuilder获取Item实际高度并更新记录列表:
顶部ListView的ItemBuilder示例:
itemBuilder: (context, index) { return LayoutBuilder( builder: (context, constraints) { WidgetsBinding.instance.addPostFrameCallback((_) { // 仅当高度变化或未记录时更新 if (_topItemHeights[index] != constraints.maxHeight) { setState(() { _topItemHeights[index] = constraints.maxHeight; }); } }); // 替换为你的实际Item Widget return YourTopVerseItem( text: widget.verseTamil[index], fontSize: fontSized, color: _fontcolor, ); }, ); },
底部ListView的ItemBuilder示例:
itemBuilder: (context, index) { return LayoutBuilder( builder: (context, constraints) { WidgetsBinding.instance.addPostFrameCallback((_) { if (_bottomItemHeights[index] != constraints.maxHeight) { setState(() { _bottomItemHeights[index] = constraints.maxHeight; }); } }); // 替换为你的实际Item Widget return YourBottomVerseItem( text: widget.verseEnglish[index], fontSize: fontSized, color: _fontcolor, ); }, ); },
4. 实现按索引同步滚动
修改NotificationListener的逻辑,不再直接同步偏移量,而是通过可见索引计算目标偏移量:
顶部列表的滚动监听:
onNotification: (notification) { if (notification is ScrollStartNotification) { if (scrollingList == ScrollingList.none) { scrollingList = ScrollingList.top; } } else if (notification is ScrollEndNotification) { if (scrollingList == ScrollingList.top) { scrollingList = ScrollingList.none; } } if (scrollingList == ScrollingList.top && _bottomItemHeights.isNotEmpty) { // 获取顶部列表第一个可见索引 final visibleIndex = _getFirstVisibleIndex(_controllerTop); // 计算底部列表到该索引的总偏移量 double targetOffset = 0; for (int i = 0; i < visibleIndex; i++) { if (i < _bottomItemHeights.length) { targetOffset += _bottomItemHeights[i]; } } // 底部列表跳转到目标偏移量 _controllerBottom.jumpTo(targetOffset); } return true; },
底部列表的滚动监听:
onNotification: (notification) { if (notification is ScrollStartNotification) { if (scrollingList == ScrollingList.none) { scrollingList = ScrollingList.bottom; } } else if (notification is ScrollEndNotification) { if (scrollingList == ScrollingList.bottom) { scrollingList = ScrollingList.none; } } if (scrollingList == ScrollingList.bottom && _topItemHeights.isNotEmpty) { // 获取底部列表第一个可见索引 final visibleIndex = _getFirstVisibleIndex(_controllerBottom); // 计算顶部列表到该索引的总偏移量 double targetOffset = 0; for (int i = 0; i < visibleIndex; i++) { if (i < _topItemHeights.length) { targetOffset += _topItemHeights[i]; } } // 顶部列表跳转到目标偏移量 _controllerTop.jumpTo(targetOffset); } return true; },
5. 实现获取第一个可见索引的方法
在_WeatherCityitemState中添加工具方法:
int _getFirstVisibleIndex(ScrollController controller) { final renderObject = controller.position.context.findRenderObject(); if (renderObject is RenderSliverList && renderObject.children.isNotEmpty) { return renderObject.indexOf(renderObject.children.first); } return 0; }
关键注意事项
- 确保两个ListView的
itemCount一致,否则索引同步会出现越界问题; - 如果Item高度会动态变化(比如字体大小调整),必须重置高度记录列表,否则同步会失效;
- 使用
jumpTo会有瞬间跳转的效果,如果需要平滑滚动,可以替换为animateTo并设置动画参数。
内容的提问来源于stack exchange,提问作者Immanuvel Blessanraj Mariappan
相关产品推荐
相关产品推荐

