flutter滚动视图之Viewport、RenderViewport源码解析(六)

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)。
  • _ViewportElementViewport 专用的 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 表示底部/右侧)。

  • cacheExtentcacheExtentStyle:控制预渲染区域,优化滚动性能。

  • addAll(children):将所有 sliver 子组件加入视口。

相关推荐
雾恋16 分钟前
我用 trae 写了一个菜谱小程序(灶搭子)
前端·javascript·uni-app
烛阴1 小时前
TypeScript 中的 `&` 运算符:从入门、踩坑到最佳实践
前端·javascript·typescript
Java 码农2 小时前
nodejs koa留言板案例开发
前端·javascript·npm·node.js
ZhuAiQuan2 小时前
[electron]开发环境驱动识别失败
前端·javascript·electron
nyf_unknown2 小时前
(vue)将dify和ragflow页面嵌入到vue3项目
前端·javascript·vue.js
胡gh2 小时前
浏览器:我要用缓存!服务器:你缓存过期了!怎么把数据挽留住,这是个问题。
前端·面试·node.js
你挚爱的强哥3 小时前
SCSS上传图片占位区域样式
前端·css·scss
奶球不是球3 小时前
css新特性
前端·css
无羡仙3 小时前
React 状态更新:如何避免为嵌套数据写一长串 ...?
前端·react.js