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 等文章, 可能有你想要了解的技能知识点哦~

相关推荐
前端李易安1 分钟前
ajax的原理,使用场景以及如何实现
前端·ajax·okhttp
汪子熙32 分钟前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ41 分钟前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
西柚与蓝莓4 小时前
任务【浦语提示词工程实践】
github
Мартин.5 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。6 小时前
案例-表白墙简单实现
前端·javascript·css
数云界6 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd6 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常6 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer6 小时前
Vite:为什么选 Vite
前端