Flutter CustomScrollView 效果-顶栏透明与标签栏吸顶

CustomScrollView 效果

1. 关键组件

CustomScrollView, SliverOverlapAbsorber, SliverPersistentHeader

2. 关键内容 TLDR

SliverOverlapAbsorber 包住 pinned为 true 的组件 可以被CustomScrollView 忽略高度。

以下的全部内容的都为了阐述上面这句话。初阶 Flutter 开发知道这句话或许可以节省数天研究时间。

pinned为 true 的组件:SliverPersistentHeaderSliverAppBar

CustomScrollView 忽略高度可以使的其他的 Sliver 组件与 pinnedtrue 的组件有重叠效果

3. 正文

先进行定义 TopBar:顶部栏, TabBar:吸顶的标签栏。 对上说效果进行说明: 刚进入页面时,TopBar 下是透明的,一般业务上用来显示 banner 或者重要的广告。 向上滑动页面,TabBar 滑动到顶部时吸顶 并且位于 TopBar 以下。 代码的结构如下

Dart 复制代码
CustomScrollView(
  controller: _controller,
  slivers: <Widget>[
        /// 顶部栏,固定在顶部
        SliverOverlapAbsorber(
                handle: SliverOverlapAbsorberHandle(),
                sliver: SliverAppBar(
                        pinned: true:
                        ...
                )
        ),
        /// 比如 Banner,金刚区等
        SliverToBoxAdapter(
          child: Image.asset(
                "./assets/images/sky.webp",
                fit: BoxFit.cover,
          ),
        ),
        
        /// 其他一些 Sliver 组件
        ...
        ///吸顶的标签栏
        SliverPersistentHeader(
                pinned: true,
                delegate: TabBarDelegate()
        ),
        /// 列表
        SliverList(),
  ],
)

在这里例子中,CutomScrollView 会忽略 SliverOverlapAbsorber 组件高度,使得之后的组件计算开始布局的位置从SliverOverlapAbsorber的上边缘开始计算。这样当 TopBar 透明时可以看到完整的 Banner 大图 。

如何使得 TabBar 吸顶并且位于 TopBar 下面呢,可以使用 SliverAppBar 作为 TopBar, 使用 SliverPersistentHeader 作为 TabBar。将两个组件中的 pinned 属性都设置为 true 之后,当页面上滑时当 TabBar 滑到 TopBar 下方时便会固定住不再向上滑动。

换个便于记忆的简单说法是,在 CustomScrollView 中,如果想要忽略高度用 SliverOverlapAbsorber ,想要吸顶就用 SliverPersistentHeader

TopBar 底部用到大图,为什么不用 SliverAppBar flexibleSpace 属性?

使用 SliverAppBar 的特性需要修改属性较多,而且把 顶部 Banner 与 SliverAppBar 混在一起并不优雅。如果只能用 flexbleSpace 实现可以如此

Dart 复制代码
 SliverAppBar(
  pinned: true,
  expandedHeight: 200,
  title: TopBar()
  flexibleSpace: FlexibleSpaceBar(
        collapseMode: CollapseMode.pin,
        background: Banner(),
  ),
)

TabBar 怎么 吸顶 的?

TabBar 之所以能吸顶是使用的 SliverPersistentHeader 中使用了 RenderSliverPinnedPersistentHeader

Dart 复制代码
class RenderSliverPinnedPersistentHeader{
  void performLayout() {
...
    geometry = SliverGeometry(
      scrollExtent: maxExtent,
      paintOrigin: constraints.overlap,
      paintExtent: math.min(childExtent, effectiveRemainingPaintExtent),
      layoutExtent: layoutExtent,
      maxPaintExtent: maxExtent + stretchOffset,
      maxScrollObstructionExtent: minExtent,
      cacheExtent: layoutExtent > 0.0 ? -constraints.cacheOrigin + layoutExtent : layoutExtent,
      hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
    );
  }
  ...
}

吸顶关键是设置 paintOrigin 属性。paintOrigin 注释如下

Dart 复制代码
  /// The visual location of the first visible part of this sliver relative to
  /// its layout position.

官方的注释说的比较严谨且抽象,我尝试解释下 直译是 sliver 的可见位置相对于布局位置。布局位置可以理解为 sliver 占据的空间,可见位置就是其绘制的位置。如果 paintOrigin 为 0,那么可见位置就是布局位置,就像普通的不吸顶的 sliver 一样。 paintOrigin: constraints.overlap 意为从距离布局位置顶部 constraints.overlap 的距离开始绘制。constraints.overlap 是两个页面重叠的距离,如果当前吸顶组件的上面没有其他可吸顶组件,则这个值就是 0,如何之前有其他的吸顶组件,比如 TopBar 也是吸顶组件,这个值就与 TopBar 的高度相同,如是 TabBar 可以悬停在 TopBar 下面。有时学习为了解释一个概念,又会引入了另外一个新概念,导致复杂度成倍增加,有时甚至会引入几十个甚至几百个新概念,尽管如此,坚持下去,才能真正理解知识,而不是原地空转。

有个 SliverPhysicalParentData.paintOffset 属性 与 paintOrigin 类似的概念也对 sliver 组件的绘制位置有影响。在 吸顶的组件(如 SliverToBoxAdapter )中 paintOffsetSliverConstraint.scrollOffset在 可以悬停吸顶的组件 SliverPersistentHeaderpaintOffsetOffset(0, 0)

Dart 复制代码
final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
childParentData.paintOffset = Offset(0, 0);

为什么 SliverOverlapAbsorber 高度会被忽略?

SliverOverlapAbsorber 实际渲染的是 RenderSliverOverlapAbsorber 。在 RenderSliverOverlapAbsorber.performLayout 中设定高度代码如下

Dart 复制代码
child!.layout(constraints, parentUsesSize: true);
final SliverGeometry childLayoutGeometry = child!.geometry!;
geometry = childLayoutGeometry.copyWith(
   scrollExtent: childLayoutGeometry.scrollExtent - childLayoutGeometry.maxScrollObstructionExtent,
   layoutExtent: math.max(0, childLayoutGeometry.paintExtent - childLayoutGeometry.maxScrollObstructionExtent),
);

可以看到 SliverOverlapAbsorber 的高度依赖于 child 的信息 scrollExtent - maxScrollObstructionExtent

child 是什么?当 SliverOverlapAbsorber 中包含的 SliverAppBar 属性 pinned 设置为 true 时,child 实际为 RenderSliverPinnedPersistentHeader

Dart 复制代码
  void performLayout() {
    ...
    geometry = SliverGeometry(
      scrollExtent: maxExtent,
      paintOrigin: constraints.overlap,
      paintExtent: math.min(childExtent, effectiveRemainingPaintExtent),
      layoutExtent: layoutExtent,
      maxPaintExtent: maxExtent + stretchOffset,
      maxScrollObstructionExtent: minExtent,
      cacheExtent: layoutExtent > 0.0 ? -constraints.cacheOrigin + layoutExtent : layoutExtent,
      hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
    );
  }

可以看到 scrollExtentmaxExtentmaxScrollObstructionExtentminExtent,当 SliverAppBar 不设置 expandedHeightmaxExtentminExtent 相等,最终 SliverOverlapAbsorber 中的 geometryscrollExtentlayoutExtent 为 0。从而 CustomScrollView 忽略了 SliverOverlapAbsorber 高度。 注意 只有 SliverOverlapAbsorber 包含的 SliverAppBar组件 pinned=true 时才会忽略 SliverAppBar 的高度,并且忽略的部分依据 maxExtentminExtent 相等时才完全忽略。

4. 结语

重要内容总结为一句话和一些 限定语

  • SliverOverlapAbsorber 可以让 CustomScrollView 忽略 其包含的固定组件高度

  • SliverOverlapAbsorber 包含的 SliverAppBar 需要设置为 pinned=true

  • SliverAppBarmaxExtentminExtent 相等时 整个 SliverAppBar 的高度都会被忽略

记住了组件的作用在用到时就可以快速完成大部分工作了,再了解了组件原理就可以更好的完成工作了,当效果偏离预想时也可以纠偏找到正确的思路。

5. 团队介绍

三翼鸟数字化技术平台-商城 」负责搭建门店数字化转型工具,包括:海尔智家体验店小程序、三翼鸟工作台APP、商家中心等产品形态,通过数字化工具,实现门店的用户上平台、交互上平台、交易上平台、交付上平台,从而助力海尔专卖店的零售转型,并实现三翼鸟店的场景创新。

相关推荐
睡觉谁叫3 小时前
一文解秘Rust如何与Java互操作
android·java·flutter·跨平台
lqj_本人13 小时前
Flutter&鸿蒙next中封装一个列表组件
flutter·华为·harmonyos
lqj_本人21 小时前
Flutter&鸿蒙next中的按钮封装:自定义样式与交互
flutter·华为·harmonyos·鸿蒙
newki1 天前
【Flutter】GoRouter的路由管理 VS AutoRouter的路由管理
前端·flutter
乐闻x2 天前
Vue 全局状态管理:Vuex 从入门到精通
前端·vue.js·flutter
Python私教2 天前
Flutter通过Flare添加2D动画
flutter
waterHBO2 天前
flutter 写个简单的界面
开发语言·javascript·flutter
python_use2 天前
Flutter学习笔记(一)-----环境配置
笔记·学习·flutter
小可以o3 天前
flutter 打包
android·flutter·ios