Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
 # 一、痛点
痛点一:Flutter 官方提供了 ScrollController,调用下方两个方法可以滚动到指定偏移处
void jumpTo(double value)
Future<void> animateTo(
  double offset, {
  required Duration duration,
  required Curve curve,
})
但是官方没有提供滚动到指定下标位置的功能
痛点二:
为了解决痛点一,业内有很多第三方库实现了这个功能,其中最知名的莫过于谷歌的 scrollable_positioned_list (opens new window)
而这些库都有一些相同的问题:
- 侵入性强,必须使用他们提供的 
Widget来构建列表视图 - 不支持 
GridView 
对此,我决定自己编写一个库(flutter_scrollview_observer (opens new window))来实现这个功能,以及解决以上的问题。
# 二、优点
- 侵入性低,不会限制你的列表视图实现,只要求将列表视图做为 
ViewObserver的child,如果不想用了,直接移除掉ViewObserver即可。 - 基于
【不会限制你的列表视图实现】这一点,flutter_scrollview_observer支持所有的列表Widget,如:ListView、GridView,甚至CustomSrollView - 防抖动,如果在滚动到指定下标时,列表尾部的可滚动区域已不足以支撑滚动到相应位置,则会自动滚动到最底部,避免回弹的问题
 
下面正式介绍一下这个库的使用
# 三、使用
# 1、ListView
 正常创建和使用 ScrollController 实例
这里你可以使用
ListView或者CustomSrollView的SliverList
ScrollController scrollController = ScrollController();
ListView _buildListView() {
  return ListView.separated(
    controller: scrollController,
    ...
  );
}
创建 ListObserverController 实例并将其传递给 ListViewObserver
ListObserverController observerController = ListObserverController(controller: scrollController);
ListViewObserver(
  controller: observerController,
  child: _buildListView(),
  ...
)
注意:创建
ListObserverController实例时需要传入列表的ScrollController实例。 这一步很重要,因为flutter_scrollview_observer内部实现原理是基于官方ScrollController提供的jumpTo和animateTo方法
现在即可滚动到指定下标位置了
// 无动画滚动至下标位置
observerController.jumpTo(index: 100)
// 动画滚动至下标位置
observerController.animateTo(
  index: 100,
  duration: const Duration(seconds: 1),
  curve: Curves.ease,
);

# 2、GridView
 由于内容与
ListView基本一致,所以这里就折叠了,需要的小伙伴可点击【展开查看】


展开查看
正常创建和使用 ScrollController 实例
Widget _buildGridView() {
  return GridView.builder(
    ...
    controller: scrollController,
    ...
  );
}
创建 GridObserverController 实例并将其传递给 GridViewObserver
GridObserverController observerController = GridObserverController(controller: scrollController);
GridViewObserver(
  controller: observerController,
  child: _buildGridView(),
)
现在即可滚动到指定下标位置了
observerController.jumpTo(
  index: 40,
);
observerController.animateTo(
  index: 40,
  duration: const Duration(seconds: 1),
  curve: Curves.ease,
);
# 3、CustomSrollView
 支持
SliverList和SliverGrid混合使用的情况!

如上图所示,CustomSrollView 的 slivers 中包含有 SliverList 和 SliverGrid
- 点击右下角第一个按钮可滚动到 
SliverList的第29行 - 点击第二个按钮可滚动到 
SliverGrid的第10的子部件所在行 
展开查看
正常创建和使用 ScrollController 实例
Widget _buildScrollView() {
  return CustomScrollView(
    controller: scrollController,
    // scrollDirection: Axis.horizontal,
    slivers: [
      _buildSliverListView(),
      _buildSliverGridView(),
    ],
  );
}
正常创建你的 SliverList 和 SliverGrid,并将它们的 BuildContext 记录起来
Widget _buildSliverListView() {
  return SliverList(
    delegate: SliverChildBuilderDelegate(
      (ctx, index) {
        _sliverListCtx ??= ctx;
        ...
      },
      ...
    ),
  );
}
Widget _buildSliverGridView() {
  return SliverGrid(
    ...
    delegate: SliverChildBuilderDelegate(
      (BuildContext context, int index) {
        _sliverGridCtx ??= context;
        ...
      },
      ...
    ),
  );
}
创建 SliverObserverController 实例并将其传递给 SliverViewObserver
注意:在 sliverListContexts 里返回刚才记录的两个 Sliver 的 BuildContext
SliverObserverController observerController = SliverObserverController(controller: scrollController);
SliverViewObserver(
  controller: observerController,
  child: _buildScrollView(),
  sliverListContexts: () {
    return [
      if (_sliverListCtx != null) _sliverListCtx!,
      if (_sliverGridCtx != null) _sliverGridCtx!,
    ];
  },
  ...
),
现在即可滚动到指定下标位置了
observerController.animateTo(
  sliverContext: _sliverListCtx,
  index: 29,
  duration: const Duration(milliseconds: 300),
  curve: Curves.easeInOut,
);
observerController.animateTo(
  sliverContext: _sliverGridCtx,
  index: 10,
  duration: const Duration(milliseconds: 300),
  curve: Curves.easeInOut,
);
# 四、说明
# 1、ViewObserver 的选择
 建议使用相应的 ViewObserver:
- 如果是 
ListView或者纯SliverList,建议使用ListViewObserver - 如果是 
GridView或者纯SliverGrid,建议使用GridViewObserver - 如果是 
SliverList和SliverGrid,则使用SliverViewObserver 
这样在 onObserve 回调中拿到数据模型后不用再对其进行类型判断与转换。
# 2、isFixedHeight
 子部件为固定高度的情况下,请设置 isFixedHeight 为 true,以提升性能!
observerController.jumpTo(
  index: 100, 
  isFixedHeight: true
);
observerController.animateTo(
  index: 100,
  isFixedHeight: true,
  duration: const Duration(seconds: 1),
  curve: Curves.ease,
);
# 3、sliverContext 是否需要传
 jumpTo({
  required int index,
  BuildContext? sliverContext,
  bool isFixedHeight = false,
})
animateTo({
  required int index,
  required Duration duration,
  required Curve curve,
  BuildContext? sliverContext,
  bool isFixedHeight = false,
})
如上,jumpTo 和 animateTo 方法中都有一个 sliverContext 参数,这是用来指定哪个 sliver 进行滚动操作的。
如果不传 sliverContext,则默认会是列表中的第一个 sliver,只有你想让非第一个 sliver 进行滚动的时候才需要传该值
# 五、可现实的功能
# 1、获得当前视窗中的子部件信息

如上图控制台所示,在滚动的过程中可以实时获取正在显示的子部件的数据。
# 2、视频列表自动播放

这种功能很常见,在列表滚动的时候,指定区域内的被命中的视频便会自动进行播放。
# 3、模块定位

一般详情页中都会有的功能,在列表视图滚动的时候,顶部定位导航视图跟着更新下标,点击导航视图某个下标则会定位到对应的模块
# 六、最后
如果这个库对你很有帮助,请不吝给个 star 吧

- 01
 - Flutter 多仓库本地 Monorepo 方案与体验优化10-25
 
- 02
 - Flutter webview 崩溃率上升怎么办?我的分析与解决方案10-19
 
- 03
 - Flutter - Melos Pub workspaces 实践10-12