Flutter - 支持观察NestedScrollView,兼容性更强 😈

欢迎关注微信公众号:FSA全栈行动 👋

系列文章

开源库: flutter_scrollview_observer

  1. Flutter - 获取ListView当前正在显示的Widget信息
  2. Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
  3. Flutter - 快速实现聊天会话列表的效果,完美💯
  4. Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
  5. Flutter - 瀑布流交替播放视频 🎞
  6. Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖
  7. Flutter - 滚动视图中的表单防遮挡 🗒
  8. Flutter - 秒杀1/2曝光统计 📊
  9. Flutter - 如何快速搓一个微信通讯录列表(azlist) 📓
  10. Flutter - 支持观察NestedScrollView,兼容性更强 😈

一、概述

时间过得真快,距离上一篇介绍 scrollview_observer 功能的文章已过去了 9个月,目前 scrollview_observer 也来到了 1.21.0 版本,现在就带大家来看看都更新了哪些内容吧

GitHub: github.com/fluttercand...

二、功能点

支持 NestedScrollView

scrollview_observer监听滚动视图中正在显示的子部件滚动到指定下标位置 这两大功能,现已对 NestedScrollView 进行了支持,如下图所示

等下一篇文章再跟大家详细介绍使用步骤 : )

支持 center

如下代码所示,在一些场景下,你可能会对 CustomScrollViewcenter 进行设置,以此来实现聊天消息页。

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 高度的 itemListView 中的 visibleMainAxisSize

visibleFraction

sliveritem 的显示占比

计算公式:visibleFraction = visibleMainAxisSize / paintExtent

如图所示,当前 SliverList 的绘制长度 paintExtent376,其 item20 的可视大小 visibleMainAxisSize30,所以 item20 的可视占比 visibleFraction30/376

SliverObserveContext

用于获取 sliverBuildContext,这在观察 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,
}

本次更新的是指定模式,因为原 refItemRelativeIndexrefItemRelativeIndexAfterUpdate 两个参数仅能表达相对下标之意,而无法表达参考坐标系,所以将其废弃。

新增 refItemIndexrefItemIndexAfterUpdate,并结合 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 指定为 1refItemIndexAfterUpdate 也指定为 1
  • itemIndex: 三种模式中最容易理解的模式,用来参照的 item 的下标是什么,你就指定什么,比如上述中 item11 发生了变化,我们想保持位置就可以拿不变的 item12 来做参照,所以 refItemIndexrefItemIndexAfterUpdate 都指定为 12

记住,不管你选择哪种参考模式,都需要注意的一点,即指定的参照 item 需要在变化前后都有被渲染,这样才能确保保持位置的功能可以正常生效!

三、最后

通过上述示例的讲解,相信你对 scrollview_observer 的使用又更加清楚,开源不易,如果你也觉得这个库好用,请不吝给个 Star 👍

GitHub: github.com/fluttercand...

本篇到此结束,感谢大家的支持,我们下次再见! 👋

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有 iOS 技术,还有 AndroidFlutterPython 等文章, 可能有你想要了解的技能知识点哦~

相关推荐
李鸿耀3 分钟前
仅用几行 CSS,实现优雅的渐变边框效果
前端
码事漫谈22 分钟前
解决 Anki 启动器下载错误的完整指南
前端
im_AMBER42 分钟前
Web 开发 27
前端·javascript·笔记·后端·学习·web
蓝胖子的多啦A梦1 小时前
低版本Chrome导致弹框无法滚动的解决方案
前端·css·html·chrome浏览器·版本不同造成问题·弹框页面无法滚动
玩代码1 小时前
vue项目安装chromedriver超时解决办法
前端·javascript·vue.js
訾博ZiBo1 小时前
React 状态管理中的循环更新陷阱与解决方案
前端
StarPrayers.2 小时前
旅行商问题(TSP)(2)(heuristics.py)(TSP 的两种贪心启发式算法实现)
前端·人工智能·python·算法·pycharm·启发式算法
一壶浊酒..2 小时前
ajax局部更新
前端·ajax·okhttp
DoraBigHead3 小时前
React 架构重生记:从递归地狱到时间切片
前端·javascript·react.js
彩旗工作室4 小时前
WordPress 本地开发环境完全指南:从零开始理解 Local by Flywhee
前端·wordpress·网站