Sliver 专题

Slivers in Flutter are a fundamental part of creating custom scroll effects in a scrollable area. (可滚动的布局中的一部分, 所以多个sliver就叫sliverList)

slivers give developers fine-grained control over scroll behavior, animation, and the geometry of scrolling elements, making them the building blocks for complex scrollable areas.

  • SliverList and SliverGrid are the sliver equivalents of ListView and GridView, respectively. They allow you to lay out items linearly or in a grid pattern.
  • SliverAppBar is a highly flexible app bar that can expand, collapse, float, and snap as you scroll.
    • 要配合stretchflexibleSpace
  • SliverToBoxAdapter allows you to place a single non-sliver widget within a CustomScrollView.
  • SliverFillRemaining and SliverFillViewport let you size children based on the remaining space in the viewport, creating dynamic effects as you scroll.

总的来说, 多个sliver组成一个ViewPort, ViewPort在一个窗口里, 只能显示一部分, 所以需要滚动, 而且是通过ScrollPosition来决定ViewPort哪些部分可见不可见, 而不是窗口与ViewPort的位置关系

ViewPort

class Viewport extends MultiChildRenderObjectWidget {
  /// 主轴方向
  final AxisDirection axisDirection;
  /// 纵轴方向
  final AxisDirection crossAxisDirection;
  /// center 决定 viewport 的 zero 基准线,也就是 viewport 从哪个地方开始绘制,默认是第一个 sliver
  /// center 必须是 viewport slivers 中的一员的 key
  final Key center;
  
/// 锚点,取值[0,1],和 zero 的相对位置,比如 0.5 代表 zero 被放到了 Viewport.height / 2 处
  final double anchor;
  /// 滚动的累计值,确切的说是 viewport 从什么地方开始显示
  final ViewportOffset offset;
  /// 缓存区域,也就是相对有头尾需要预加载的高度
  final double cacheExtent;
  /// children widget
  List<Widget> slivers;
  }

ViewportOffset (ScrollPosition)

ScrollPosition 决定了 Viewport 哪些区域是可见的,它包含了Viewport 的滚动信息.

再次重申, ViewPort自己决定能显示多少, 而不是自己全部显示, 只是被上层的窗口或容器阻挡住了

abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
  // 滚动偏移量
  double _pixels;
  // 设置滚动响应效果,比如滑动停止后的动画
  final ScrollPhysics physics;
  // 保存当前的滚动偏移量到 PageStore 中,当 Scrollable 重建后可以恢复到当前偏移量
  final bool keepScrollOffset;
  // 最小滚动值
  double _minScrollExtent;
  // 最大滚动值
  double _maxScrollExtent;
  ...
}

Scrollable

Scrollable 是一个可滚动的 Widget,它主要负责:

  • 监听用户的手势,计算滚动状态发出 Notification
  • 计算 offset 通知 listeners

Scrollable 本身不具有绘制内容的能力,它通过构造注入的 viewportBuilder 来创建一个 Viewport 来显示内容,当滚动状态变化的时候,Scrollable 就会不断的更新 Viewport 的 offset ,Viewport 就会不断的更新显示内容。

SliverConstraints

和 Box 布局使用 BoxConstraints 作为约束类似,Sliver 布局采用 SliverConstraints 作为约束,但相对于 Box 要复杂的多,可以理解为 SliverConstraints 描述了 Viewport 和它内部的 Slivers 之间的布局信息

class SliverConstraints extends Constraints {
  // 主轴方向
  final AxisDirection axisDirection;
  // 窗口增长方向
  final GrowthDirection growthDirection;
  // 如果 Direction 是 AxisDirection.down,scrollOffset 代表 sliver 的 top 滑过 viewport 的 top 的值,没滑过 viewport 的 top 时 scrollOffset 为 0。
  final double scrollOffset;
  // 上一个 sliver 覆盖下一个 sliver 的大小(只有上一个 sliver 是 pinned/floating 才有效)
  final double overlap;
  // 轮到当前 sliver 开始绘制了,需要 viewport 告诉 sliver 当前还剩下多少区域可以绘制,受 viewport 的 size 影响
  final double remainingPaintExtent;
  // viewport 主轴上的大小
  final double viewportMainAxisExtent;
  // 缓存区起点(相对于 scrolloffset),如果 cacheExtent 设置为 0,那么 cacheOrigin 一直为 0
  final double cacheOrigin;
  // 剩余的缓存区大小
  final double remainingCacheExtent;

  ...
}

SliverGeometry

Viewport 通过 SliverConstraints 告知它内部的 sliver 自己的约束信息,比如还有多少空间可用、offset 等,那么 Sliver 则通过 SliverGeometry 反馈给 Viewport 需要占用多少空间量。


class SliverGeometry extends Diagnosticable {
  // sliver 可以滚动的范围,可以认为是 sliver 的高度(如果是 AxisDierction.Down) 
  final double scrollExtent;
  // 绘制起点(默认是 0.0),是相对于 sliver 开始 layout 的起点而言的,不会影响下一个 sliver 的 layoutExtent,会影响下一个 sliver 的paintExtent
  final double paintOrigin;
  // 绘制范围
  final double paintExtent;
  // 布局范围,当前 sliver 的 top 到下一个 sliver 的 top 的距离,范围是[0,paintExtent],默认是 paintExtent,会影响下一个 sliver 的 layout 位置
  final double layoutExtent;
  // 最大绘制大小,必须 >= paintExtent
  final double maxPaintExtent;
  // 如果 sliver 被 pinned 在边界的时候,这个大小为 Sliver 的自身的高度,其他情况为0,比如 pinned app bar
  final double maxScrollObstructionExtent;
  // 点击有效区域的大小,默认为paintExtent
  final double hitTestExtent;
  // 是否可见,visible = (paintExtent > 0)
  final bool visible;
  // 是否需要做clip,免得chidren溢出
  final bool hasVisualOverflow;
  // 当前 sliver 占用了 SliverConstraints.remainingCacheExtent 多少像素值
  final double cacheExtent;
  ...
}

约束告诉sliver怎么摆, sliver告诉约束自己多大

  • 给 sliver1 输入 SliverConstrains1 并且得到输出结果(SliverGeometry1) ,
  • 根据 SliverGeometry1 重新生成一个新的 SliverConstrains2 输入给 sliver2 得到 SliverGeometry2
  • 直至最后一个 sliver, 具体的过程可以查看 RenderViewport 的 layoutChildSequence 方法。

点这里看余下全文


Backlinks