FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • AndroidUI

  • Android第三方SDK

  • Android混淆

  • Android仓库

  • Android新闻

  • Android系统开发

  • Android源码

  • Android注解AOP

  • Android脚本

  • AndroidTv开发

  • AndroidNDK

  • Android音视频

  • Android热修复

  • Android性能优化

  • Android云游戏

  • Android插件化

  • iOSUI

  • iOS工具

  • iOS底层原理与应用

  • iOS组件化

  • iOS音视频

  • iOS疑难杂症

  • iOS之Swift

  • iOS之RxSwift

  • iOS开源项目

  • iOS逆向

  • Flutter开发

    • Dart - 抽象类的实例化
    • Flutter - 打印好用的Debug日志
    • Flutter - 混合开发
    • Flutter - 解决混合开发iOS脚本打包遇到的问题
    • Flutter - 低版本在iOS14上遇到的问题与解决方案
    • Flutter - 解决原生弹窗的触摸事件被Flutter响应的问题
    • Flutter - 实现列表上下拉切换header
    • Flutter - 获取ListView当前正在显示的Widget信息
    • Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
    • Flutter - 快速实现聊天会话列表的效果,完美💯
    • Flutter - 聊天输入框更新文本时的必备优化点🔖
    • Flutter - 我给官方提PR,解决run命令卡住问题 😃
    • Flutter - 探索run命令到底做了什么 🤔
    • Flutter - 引擎调试(iOS篇)🛠
    • Flutter - 引擎调试bug到提交PR实战 🐞
    • Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
    • Flutter - 瀑布流交替播放视频 🎞
    • Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖
    • Flutter - 滚动视图中的表单防遮挡 🗒
    • Flutter - 秒杀1/2曝光统计 📊
    • 一天内加入 Flutter 和 FlutterCandies 两大组织是什么体验 🧐
    • Flutter - 如何快速搓一个微信通讯录列表(azlist) 📓
    • Flutter - 混编项目集成Shorebird热更新🐦(安卓篇)
    • Flutter - 混编项目集成Shorebird热更新🐦(iOS篇)
    • Flutter - 解决返回原生页面时dispose方法未被触发的问题 🐞
    • Flutter - 升级3.19之后页面多次rebuild?🤨
    • Flutter - 热更新 Shorebird 1.0 正式版来了 🐦
    • Flutter - 使用Pigeon实现视频缓存插件 🐌
    • Flutter - 轻松搞定屏幕旋转功能 😎
    • Flutter - 解决Connection closed before full header was received
    • Flutter - 实现聊天键盘与功能面板的丝滑切换 🍻
    • Flutter - 支持观察NestedScrollView,兼容性更强 😈
      • 一、概述
      • 二、功能点
        • 支持 NestedScrollView
        • 支持 center
        • observeIntervalForScrolling
        • visibleMainAxisSize
        • visibleFraction
        • SliverObserveContext
        • 聊天保持位置功能
      • 三、最后
    • Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻
    • Flutter - 升级到3.24后页面还会多次rebuild吗?🧐
    • Flutter - 轻松实现PageView卡片偏移效果
    • Flutter - 轻松搞定炫酷视差(Parallax)效果
    • Flutter - 危!3.24版本苹果审核被拒!
    • Flutter - 子部件任意位置观察滚动数据
    • Flutter - iOS编译加速
    • Flutter - Xcode16 还原编译速度
  • 移动端
  • Flutter开发
LinXunFeng
2024-07-06
目录

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

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

# 一、概述

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

GitHub: https://github.com/fluttercandies/flutter_scrollview_observer

# 二、功能点

# 支持 NestedScrollView

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

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

# 支持 center

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

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

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 的选中下标。

/// 记录各 Sliver 的下标与 BuildContext
Map<int, BuildContext> sliverIndexCtxMap = {};

SliverViewObserver(
  controller: sliverObserverController,
  sliverContexts: () => sliverIndexCtxMap.values.toList(),
  child: child,
  onObserveViewport: (result) {
    ...
  },
);

但在研发过程中,我们很有可能会给 SliverGrid 再加一层 Sliver 去添加装饰、间距等,而 onObserveViewport 只认最外层的 sliver,所以在这里我们就用 SliverObserveContext 去进行包裹成为最外层,在其 onObserve 回调中就可以拿到对应的 BuildContext 并记录起来。

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;
}

# 聊天保持位置功能

保持位置功能目前有三种模式可选

enum ChatScrollObserverHandleMode {
  /// 常规模式
  /// 来一条消息就插入一条消息
  normal,

  /// 生成式模式
  /// 比如 ChatGPT 这种流式自更新的消息
  generative,

  /// 指定模式
  /// 可以灵活指定用来做为参照的消息item
  specified,
}

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

新增 refItemIndex 与 refItemIndexAfterUpdate,并结合 refIndexType 来更好地指定参考 item。

我们先来看一下 refIndexType 的类型定义

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: https://github.com/fluttercandies/flutter_scrollview_observer (opens new window)

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

#Dart#Flutter
Flutter - 实现聊天键盘与功能面板的丝滑切换 🍻
Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻

← Flutter - 实现聊天键盘与功能面板的丝滑切换 🍻 Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻→

最近更新
01
Flutter - Xcode16 还原编译速度
04-05
02
AI - 免费的 Cursor 平替方案
03-30
03
Android - 2025年安卓真的闭源了吗
03-28
更多文章>
Theme by Vdoing | Copyright © 2020-2025 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×