Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
# 一、概述
感谢大家对 scrollview_observer (opens new window) 的支持,上一篇关于 scrollview_observer (opens new window) 的最新一篇文章是在2022年的10月份,不知不觉已经过去了8个月,它现在已经变得更加完善和强大,让我们一起来看看都做了哪些更新吧 🥳
版本信息
- 当前版本:
1.13.2 - GitHub地址: https://github.com/LinXunFeng/flutter_scrollview_observer (opens new window)
# 二、自定义触发观察的时机
未添加该功能前,滚动视图在整个滚动过程中都会被观察,计算找出第一个 item 和正在展示的所有 item,当第一个 item 或者正在展示的 item 的对象发生变化时才会调用 [onObserve] 和 [onObserveAll] 回调。
但往往在一些场景下,我们想要指定在某个滚动状态下才进行观察,以及观察回调的触发时机,那该怎么办呢?
# 1、autoTriggerObserveTypes 参数
现在你可以通过该参数设置自动触发观察的时机,定义如下:
final List<ObserverAutoTriggerObserveType>? autoTriggerObserveTypes;
enum ObserverAutoTriggerObserveType {
scrollStart,
scrollUpdate,
scrollEnd,
}
其默认值为 [.scrollStart, .scrollUpdate, .scrollEnd]
枚举值说明:
| 枚举值 | 描述 |
|---|---|
scrollStart | 开始滚动 |
scrollUpdate | 滚动中 |
scrollEnd | 结束滚动 |
如在视频列表的场景中,我们可能只需要在列表结束滚动时再去做观察哪个 item 是第一个,然后进行自动播放,这个时候设置 autoTriggerObserveTypes 为 .scrollEnd 即可,同时也避免了大量不必要的计算。
# 2、triggerOnObserveType 参数
用于配置触发 [onObserve] 和 [onObserveAll] 回调的前提,定义如下:
final ObserverTriggerOnObserveType triggerOnObserveType;
enum ObserverTriggerOnObserveType {
directly,
displayingItemsChange,
}
其默认值为 .displayingItemsChange
枚举值说明:
| 枚举值 | 描述 |
|---|---|
directly | 观察到数据后直接将数据返出 |
displayingItemsChange | 当列表子部件进出或数量发生变化时将观察到的数据返出 |
一般是在实时获取 item 的 leadingMarginToViewport(item 顶部与视窗顶部的距离) 和 trailingMarginToViewport(item 底部与视窗底部的距离)的时候比较常用。

# 三、保持IM会话位置功能增强
之前限制只支持插入一条消息时保持会话位置,现在放开支持多条,使用方式不变,效果如下

注:该功能依赖被插入消息前的最新消息视图做为参照去计算偏移量,所以如果一次性插入的消息数太多,导致该参照消息视图无法得到渲染,则该功能会失效,需要你自己去对
ScrollView的cacheExtent设置合理的值来尽量避免这个问题!
# 四、支持观察瀑布流
原先仅针对 GridView 这种常规网格布局,只处理顶部偏移量一致的 item,现在调整了内部处理逻辑,已支持瀑布流这种模式

如上图红线所示,位于第一的 item 是 grid item2 和 grid item3。
# 五、支持观察viewport
大家多多少少会遇到这种情况,在 CustomScrollView 中放置了好多 sliver,需要对它们进行观察,判断哪些 sliver 正在展示,而不仅仅是观察 SliverList 和 SliverGrid 中的 item。
在这里可以使用 onObserveViewport 回调得到你想要的结果,代码如下
SliverViewObserver(
child: _buildScrollView(),
sliverContexts: () {
return [
if (grid1Context != null) grid1Context!,
if (swipeContext != null) swipeContext!,
if (grid2Context != null) grid2Context!,
];
},
onObserveViewport: (result) {
firstChildCtxInViewport = result.firstChild.sliverContext;
if (firstChildCtxInViewport == grid1Context) {
debugPrint('current first sliver in viewport - gridView1');
} else if (firstChildCtxInViewport == swipeContext) {
debugPrint('current first sliver in viewport - swipeView');
} else if (firstChildCtxInViewport == grid2Context) {
debugPrint('current first sliver in viewport - gridView2');
}
},
)
- 在
sliverContexts回调中返回需要观察的sliver的BuildContext。 - 在
onObserveViewport回调中拿到观察结果,结果对应的类为SliverViewportObserveModel
class SliverViewportObserveModel {
/// 当前 CustomScrollView 所对应的 viewport 实例.
final RenderViewportBase viewport;
/// 根据 sliverContexts,返回当前观察到的第一个 sliver
final SliverViewportObserveDisplayingChildModel firstChild;
/// 根据 sliverContexts,返回当前观察到的正在展示的所有 sliver
final List<SliverViewportObserveDisplayingChildModel>
displayingChildModelList;
...
}
这里需要注意的是,观察的对象不应为嵌套的 sliver,如下代码所示:
SliverPadding(
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(title: Text('index - $index'));
},
),
),
padding: const EdgeInsets.all(8),
);
你需要观察的是 SliverPadding,而不是 SliverList,在给 sliverContexts 传递数据时需要留意,否则观察无效。
那一块的 Sliver 的 BuildContext 要怎么获取?
你可以使用 GlobalKey,但这里更建议使用 SliverLayoutBuilder
SliverLayoutBuilder(
builder: (context, _) {
// 将 context 记录起来
if (sliverCtx != context) sliverCtx = context;
SliverPadding(
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
...
),
),
padding: const EdgeInsets.all(8),
);
},
);
# 六、支持自定义观察对象和观察逻辑
# 1、customTargetRenderSliverType 回调
仅支持
ListViewObserver和GridViewObserver
在保持原来的观察逻辑上,告诉 scrollview_observer 要处理的 RenderSliver 类型,目的是为了支持对第三方库构建的滚动视图进行观察。
customTargetRenderSliverType: (renderObj) {
// 告诉该库它需要观察什么类型的 RenderObject
// 这里以观察 loading_more_list 的 ListView 为例
return renderObj is ExtendedRenderSliverList;
},
这里罗列几个在 loading_more_list 中常用的 RenderObject 供大家对照使用。
| 类型 | RenderObject |
|---|---|
ListView | ExtendedRenderSliverList |
GridView | ExtendedRenderSliverGrid |
WaterfallFlow | RenderSliverWaterfallFlow |
# 2、customHandleObserve 回调
该回调用于自定义观察逻辑,当自带的处理逻辑不符合你的需求时使用。
customHandleObserve: (context) {
// 可以完全自定义你的观察逻辑
final _obj = context.findRenderObject();
if (_obj is RenderSliverList) {
// 可以使用该库提供的默认处理方式
ObserverCore.handleListObserve(context: context);
}
if (_obj is RenderSliverGrid || _obj is RenderSliverWaterfallFlow) {
// 可以使用该库提供的默认处理方式
return ObserverCore.handleGridObserve(context: context);
}
// 处理一些由第三方构建的 Sliver
...
// 其它类型不做处理
return null;
},
现已将原处理 RenderSliverList 和 RenderSliverGride 的逻辑抽离至 ObserverCore 中供大家自由组合使用。
# 3、extendedHandleObserve 回调
仅支持
SliverViewObserver
该回调用于对原来的观察逻辑进行补充,原来只处理 RenderSliverList、RenderSliverFixedExtentList 和 RenderSliverGrid。
extendedHandleObserve: (context) {
// 在对原来的观察逻辑进行拓展
final _obj = context.findRenderObject();
if (_obj is RenderSliverWaterfallFlow) {
return ObserverCore.handleGridObserve(context: context);
}
return null;
},
结合 ObserverCore 和以上的几个功能点,实现了可自由观察由第三方构建的滚动视图的功能,不再局限于官方的 SliverList 和 SliverGrid。
这是一个很有用的功能,它让你的滚动视图的构建变得更加自由,比如观察使用 fluttercandies/waterfall_flow (opens new window) 构建的瀑布流。
# 七、定位Tab的下标计算支持网格类型
对 ObserverUtils.calcAnchorTabIndex 进行增强,现支持处理列表和网格的模块下标计算,这里直接看效果图

GitHub: https://github.com/LinXunFeng/flutter_scrollview_observer (opens new window)

- 01
- Flutter webview 崩溃率上升怎么办?我的分析与解决方案10-19
- 02
- Flutter - Melos Pub workspaces 实践10-12
- 03
- Flutter - 详情页初始锚点与优化08-24