欢迎关注微信公众号:FSA全栈行动 👋
系列文章
开源库: flutter_scrollview_observer
- Flutter - 获取ListView当前正在显示的Widget信息
- Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
- Flutter - 快速实现聊天会话列表的效果,完美💯
- Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
- Flutter - 瀑布流交替播放视频 🎞
- Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖
- Flutter - 滚动视图中的表单防遮挡 🗒
- Flutter - 秒杀1/2曝光统计 📊
- Flutter - 如何快速搓一个微信通讯录列表(azlist) 📓
- Flutter - 支持观察NestedScrollView,兼容性更强 😈
一、概述
时间过得真快,距离上一篇介绍 scrollview_observer 功能的文章已过去了 9个月,目前 scrollview_observer 也来到了 1.21.0 版本,现在就带大家来看看都更新了哪些内容吧
GitHub: github.com/fluttercand...
二、功能点
支持 NestedScrollView
scrollview_observer 的 监听滚动视图中正在显示的子部件 与 滚动到指定下标位置 这两大功能,现已对 NestedScrollView 进行了支持,如下图所示
等下一篇文章再跟大家详细介绍使用步骤 : )
支持 center
如下代码所示,在一些场景下,你可能会对 CustomScrollView 的 center 进行设置,以此来实现聊天消息页。
dart
Widget _buildScrollView() {
return CustomScrollView(
center: _centerKey,
anchor: 1,
controller: scrollController,
slivers: [
_buildSliverListView(
color: Colors.redAccent,
onBuild: (ctx) {
_sliverListCtx1 = ctx;
},
),
_buildSliverListView(
color: Colors.blueGrey,
onBuild: (ctx) {
_sliverListCtx2 = ctx;
},
),
// center
SliverPadding(padding: EdgeInsets.zero, key: _centerKey),
_buildSliverListView(
color: Colors.teal,
onBuild: (ctx) {
_sliverListCtx3 = ctx;
},
),
_buildSliverListView(
color: Colors.purple,
onBuild: (ctx) {
_sliverListCtx4 = ctx;
},
),
],
);
}
但又想使用下标跳转 item 的功能,放心,scrollview_observer 具有良好的兼容性,在 1.19.1 版本下就已得到支持,可随便你如何设置 center,不需要额外做其它操作。
observeIntervalForScrolling
相信大家都还记得,自动触发观察的时机有以下三种
| 枚举值 | 描述 |
|---|---|
scrollStart |
开始滚动 |
scrollUpdate |
滚动中 |
scrollEnd |
结束滚动 |
其中 scrollUpdate 触发观察太过于频繁,其实很多次观察结果并不会相差多少,在大多数使用场景下,对我们来说也不太重要。
为此在本次更新中为 ObserverController 新增了 observeIntervalForScrolling 属性,用来设置触发观察的间隔,从而大量减少不必要的观察计算。
需要注意以下两点:
- 为了不改变之前的行为,所以默认值为
Duration.zero,所以大家需要自行调整,推荐设置为Duration(milliseconds: 500)。 - 该属性仅对
scrollUpdate有效。
visibleMainAxisSize
item显示的尺寸大小
如图所示,各固定 200 高度的 item 在 ListView 中的 visibleMainAxisSize。
visibleFraction
在
sliver中item的显示占比计算公式:visibleFraction = visibleMainAxisSize / paintExtent
如图所示,当前 SliverList 的绘制长度 paintExtent 为 376,其 item20 的可视大小 visibleMainAxisSize 为 30,所以 item20 的可视占比 visibleFraction 为 30/376。
SliverObserveContext
用于获取 sliver 的 BuildContext,这在观察 sliver 的场景下非常有用。
如下 CustomScrollView 配置了多个 sliver
dart
Widget _buildScrollView() {
return CustomScrollView(
controller: scrollController,
physics: const ClampingScrollPhysics(),
slivers: [
// banner
SliverPersistentHeader(...),
// 中间任意视图
SliverObserveContextToBoxAdapter(...),
// tabBar
SliverPersistentHeader(...),
// 构建多个 SliverGird
...List.generate(modelList.length, (mainIndex) {
return _buildSectionGridView(mainIndex);
}),
],
);
}
我们需要观察当前哪个 SliverGrid 是第一个,然后去同步更新 TabBar 的选中下标。
dart
/// 记录各 Sliver 的下标与 BuildContext
Map<int, BuildContext> sliverIndexCtxMap = {};
SliverViewObserver(
controller: sliverObserverController,
sliverContexts: () => sliverIndexCtxMap.values.toList(),
child: child,
onObserveViewport: (result) {
...
},
);
但在研发过程中,我们很有可能会给 SliverGrid 再加一层 Sliver 去添加装饰、间距等,而 onObserveViewport 只认最外层的 sliver,所以在这里我们就用 SliverObserveContext 去进行包裹成为最外层,在其 onObserve 回调中就可以拿到对应的 BuildContext 并记录起来。
dart
Widget _buildSectionGridView(int mainIndex) {
Widget resultWidget = SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(...),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// 拿到了 SliverGrid 的 BuildContext
return Container(
...
);
},
childCount: 10,
),
);
resultWidget = SliverPadding(
padding: const EdgeInsets.all(8),
sliver: resultWidget,
);
// 在最外层使用 SliverObserveContext 进行包裹,以获取是外层的 BuildContext。
resultWidget = SliverObserveContext(
onObserve: (context) {
sliverIndexCtxMap[mainIndex] = context;
},
child: resultWidget,
);
return resultWidget;
}
聊天保持位置功能
保持位置功能目前有三种模式可选
dart
enum ChatScrollObserverHandleMode {
/// 常规模式
/// 来一条消息就插入一条消息
normal,
/// 生成式模式
/// 比如 ChatGPT 这种流式自更新的消息
generative,
/// 指定模式
/// 可以灵活指定用来做为参照的消息item
specified,
}
本次更新的是指定模式,因为原 refItemRelativeIndex 和 refItemRelativeIndexAfterUpdate 两个参数仅能表达相对下标之意,而无法表达参考坐标系,所以将其废弃。
新增 refItemIndex 与 refItemIndexAfterUpdate,并结合 refIndexType 来更好地指定参考 item。
我们先来看一下 refIndexType 的类型定义
dart
enum ChatScrollObserverRefIndexType {
/// relativeIndex trailing
///
/// 6 | item16 | cacheExtent
/// ----------------- -----------------
/// 5 | item15 |
/// 4 | item14 |
/// 3 | item13 | 正在显示中的item
/// 2 | item12 |
/// 1 | item11 |
/// ----------------- -----------------
/// 0 | item10 | cacheExtent <---- start
///
/// leading
relativeIndexStartFromCacheExtent,
/// relativeIndex trailing
///
/// 5 | item16 | cacheExtent
/// ----------------- -----------------
/// 4 | item15 |
/// 3 | item14 |
/// 2 | item13 | 正在显示中的item
/// 1 | item12 |
/// 0 | item11 | <---- start
/// ----------------- -----------------
/// -1 | item10 | cacheExtent
///
/// leading
relativeIndexStartFromDisplaying,
/// 直接指定 item 的下标
itemIndex,
}
如上,一共有 3种 参考模式供你选择
relativeIndexStartFromCacheExtent: 从渲染区的item开始计算下标。这种模式一般用于普通消息插入,因为插入消息必定是在0处,插入消息前后不变的就是原来的最新消息,其下标从0变成了1,此时refItemIndex可指定为0,而refItemIndexAfterUpdate指定为1。relativeIndexStartFromCacheExtent: 从展示区的item开始计算下标。该模式比较少用,一般是结合观察功能,因为通过观察功能,我们是可以轻松得知正在显示的item有哪些,假设你此时对正在显示的第一个item做了内容变更,但又不想影响第二个正在显示的item的偏移,那这个模式正好适合当前这种的场景。因为改变前后不变的是第二个正在显示的item,所以refItemIndex指定为1,refItemIndexAfterUpdate也指定为1。itemIndex: 三种模式中最容易理解的模式,用来参照的item的下标是什么,你就指定什么,比如上述中item11发生了变化,我们想保持位置就可以拿不变的item12来做参照,所以refItemIndex和refItemIndexAfterUpdate都指定为12。
记住,不管你选择哪种参考模式,都需要注意的一点,即指定的参照 item 需要在变化前后都有被渲染,这样才能确保保持位置的功能可以正常生效!
三、最后
通过上述示例的讲解,相信你对 scrollview_observer 的使用又更加清楚,开源不易,如果你也觉得这个库好用,请不吝给个 Star 👍
GitHub: github.com/fluttercand...
本篇到此结束,感谢大家的支持,我们下次再见! 👋
如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有
iOS技术,还有Android,Flutter,Python等文章, 可能有你想要了解的技能知识点哦~