css
/// 一个组件,通过它可以查看更大内容的一部分,通常与 [Scrollable] 配合使用。
///
/// [Viewport] 是滚动机制中负责显示内容的核心组件。
/// 它根据自身尺寸和给定的 [offset] 显示子组件的一个子集。
/// 当偏移量(offset)变化时,不同的子组件会通过视口显示出来。
///
/// [Viewport] 托管一个双向的 sliver 列表,以 [center] sliver 为锚点,
/// 该中心 sliver 放置在零滚动偏移位置。中心组件在视口中的显示位置
/// 由 [anchor] 属性控制。
///
/// 在子组件列表中位于 [center] 之前的 sliver,会按照 [axisDirection] 的反方向
/// 从中心开始反向显示。例如,如果 [axisDirection] 为 [AxisDirection.down],
/// 那么位于中心之前的第一个 sliver 会显示在中心上方。
/// 位于 [center] 之后的 sliver 则按照 [axisDirection] 顺序显示。例如在上面的场景中,
/// 中心之后的第一个 sliver 会显示在中心下方。
///
/// [Viewport] 不能直接包含 box 类型子组件。
/// 需要使用 [SliverList]、[SliverFixedExtentList]、[SliverGrid] 或 [SliverToBoxAdapter] 来放置 box 类型组件。
///
/// 另请参见:
///
/// * [ListView]、[PageView]、[GridView] 和 [CustomScrollView],它们将
/// [Scrollable] 和 [Viewport] 组合成更易用的组件。
/// * [SliverToBoxAdapter],允许将 box 类型组件放入 sliver 上下文(与本组件相反)。
/// * [ShrinkWrappingViewport],[Viewport] 的一种变体,沿主轴收缩以包裹内容。
/// * [ViewportElementMixin],应混入视口类组件使用的 [Element] 类型,以正确处理滚动通知。
Viewport
kotlin
class Viewport extends MultiChildRenderObjectWidget {
/// 创建一个"内部更大"的组件。
///
/// Viewport 会监听 [offset],这意味着当 [offset] 改变时,
/// 不需要重新构建这个组件。
///
/// 如果 [cacheExtentStyle] 不是 [CacheExtentStyle.pixel],
/// 则必须指定 [cacheExtent]。
Viewport({
super.key,
this.axisDirection = AxisDirection.down, // 滚动方向,默认为向下
this.crossAxisDirection, // 横轴方向,可选
this.anchor = 0.0, // 视口锚点,范围 [0, 1]
required this.offset, // 滚动偏移
this.center, // 双向滚动中心 sliver
this.cacheExtent, // 缓存区域大小
this.cacheExtentStyle = CacheExtentStyle.pixel, // 缓存计算方式
this.clipBehavior = Clip.hardEdge, // 裁剪行为
List<Widget> slivers = const <Widget>[], // sliver 列表
}) : assert(
center == null || slivers.where((Widget child) => child.key == center).length == 1,
// 如果指定了 center,则在 slivers 列表中必须唯一
),
assert(
cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null,
// 如果缓存方式为 viewport,则必须指定 cacheExtent
),
super(children: slivers);
}
createRenderObject
less
RenderViewport createRenderObject(BuildContext context) {
return RenderViewport(
axisDirection: axisDirection, // 滚动方向(上下或左右)
crossAxisDirection:
crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
// 横轴方向,如果未指定则使用默认方向
anchor: anchor, // 视口锚点(0~1),决定内容在视口中的对齐位置
offset: offset, // 滚动偏移量,控制显示内容位置
cacheExtent: cacheExtent, // 预渲染缓存区域大小
cacheExtentStyle: cacheExtentStyle, // 缓存计算方式
clipBehavior: clipBehavior, // 裁剪行为
);
}
updateRenderObject
ini
void updateRenderObject(BuildContext context, RenderViewport renderObject) {
renderObject
..axisDirection = axisDirection
// 更新滚动方向(上下或左右)
..crossAxisDirection =
crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
// 更新横向方向,如果未指定则使用默认方向
..anchor = anchor
// 更新视口锚点(0~1),决定内容在视口中的对齐位置
..offset = offset
// 更新滚动偏移量
..cacheExtent = cacheExtent
// 更新预渲染缓存区域大小
..cacheExtentStyle = cacheExtentStyle
// 更新缓存计算方式
..clipBehavior = clipBehavior;
// 更新裁剪行为
}
createElement
scss
MultiChildRenderObjectElement createElement() => _ViewportElement(this);
- 这行代码表示
Viewport
widget 会创建一个_ViewportElement
作为其 Element。 Element
是 Flutter 构建 UI 的核心,用于连接 widget 和渲染对象(RenderObject)。_ViewportElement
是Viewport
专用的 Element 类型,用于管理子 sliver 的生命周期和渲染更新。- 使用
MultiChildRenderObjectElement
表明Viewport
可以拥有多个子组件(slivers)。
简单理解就是:
Widget (
Viewport
) → Element (_ViewportElement
) → RenderObject (RenderViewport
)
RenderViewport
ruby
/// 一个"内部更大"的渲染对象。
///
/// [RenderViewport] 是滚动机制中负责可视化显示的核心组件。
/// 它根据自身尺寸和给定的 [offset] 显示子组件的一个子集。
/// 随着偏移量(offset)的变化,不同的子组件会通过视口显示出来。
///
/// [RenderViewport] 在单一的共享 [Axis] 上托管双向 sliver 列表,
/// 以 [center] sliver 为锚点,该中心 sliver 放置在零滚动偏移位置。
/// 中心组件在视口中的显示位置由 [anchor] 属性控制。
///
/// 位于 [center] 之前的 sliver 会按照 [axisDirection] 的反方向
/// 从中心开始反向显示。例如,如果 [axisDirection] 为 [AxisDirection.down],
/// 那么中心之前的第一个 sliver 会显示在中心上方。
/// 位于 [center] 之后的 sliver 则按照 [axisDirection] 顺序显示。例如在上述场景中,
/// 中心之后的第一个 sliver 会显示在中心下方。
///
/// {@macro flutter.rendering.GrowthDirection.sample}
///
/// [RenderViewport] 不能直接包含 [RenderBox] 子组件。
/// 需要使用 [RenderSliverList]、[RenderSliverFixedExtentList]、[RenderSliverGrid]
/// 或 [RenderSliverToBoxAdapter] 来放置 box 类型渲染对象。
///
/// 另请参见:
///
/// * [RenderSliver],详细介绍 Sliver 协议。
/// * [RenderBox],详细介绍 Box 协议。
/// * [RenderSliverToBoxAdapter],允许将 [RenderBox] 放入 [RenderSliver] 中(与本类相反)。
/// * [RenderShrinkWrappingViewport],[RenderViewport] 的变体,沿主轴收缩包裹内容。
RenderViewport
scala
class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentData> {
/// 为 [RenderSliver] 对象创建一个视口。
///
/// 如果未指定 [center],则使用 `children` 列表中的第一个子组件(如果有的话)。
///
/// 必须指定 [offset]。在测试场景下,可以传入 [ViewportOffset.zero] 或 [ViewportOffset.fixed]。
RenderViewport({
super.axisDirection, // 滚动方向
required super.crossAxisDirection, // 横轴方向
required super.offset, // 滚动偏移
double anchor = 0.0, // 视口锚点,范围 [0,1]
List<RenderSliver>? children, // sliver 子组件列表
RenderSliver? center, // 中心 sliver
super.cacheExtent, // 缓存区域大小
super.cacheExtentStyle, // 缓存计算方式
super.clipBehavior, // 裁剪行为
}) : assert(anchor >= 0.0 && anchor <= 1.0),
assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null),
_anchor = anchor,
_center = center {
addAll(children); // 添加所有 sliver 子组件
if (center == null && firstChild != null) {
_center = firstChild; // 如果未指定中心,则第一个子组件作为中心
}
}
}
-
RenderViewport
是专门为RenderSliver
设计的渲染视口。 -
center
:双向滚动的锚点,如果未指定,默认使用第一个子组件。 -
offset
:滚动偏移量,控制视口显示的内容位置。 -
anchor
:决定内容在视口中的对齐方式(0 表示顶部/左侧,1 表示底部/右侧)。 -
cacheExtent
与cacheExtentStyle
:控制预渲染区域,优化滚动性能。 -
addAll(children)
:将所有 sliver 子组件加入视口。