Flutter 源码梳理系列(三十四):ContainerLayer

前言

ContainerLayer 作为 PaintingContext 构造函数的必传参数,我们来学习一下。只有 ContainerLayer 的子类可以在 Layer Tree 中拥有子级,所有其他 Layer 类都用作 Layer Tree 中的叶子节点。

ContainerLayer

ContainerLayer 是一个具有子节点列表的 Layer 子类(这里注意一下,子 Layer 列表的组织形式不像是 MultiChildRenderObjectElement 的子级 Element 列表是一个 List 类型的属性:late List<Element> _children,ContainerLayer 的子 Layer 列表是一个双向链表的结构,而这个双向链表的结构的代码实现就是通过 Layer 的 previousSibling 和 nextSibling 两个属性来实现的)。ContainerLayer 实例仅接受一个子 Layer 列表,并按顺序将它们插入到复合渲染中。有一些 ContainerLayer 的子类在此过程中应用更复杂的效果。

Constructors

ContainerLayer 直接继承自 Layer,并且作为 PaintingContext 的必传参数,所以它是我们在上层开发中会真实使用的类,下面一起来看一下它的其它内容。

dart 复制代码
class ContainerLayer extends Layer {
  // ...
}

_fireCompositionCallbacks

在 ContainerLayer 的 buildScene 和 detach 函数中会执行此函数,即当进行 Layer 的合成时进行回调,此函数是重写的 Layer 类的,到了 ContainerLayer 这里,则还需要沿着自己的子级链对所有子级进行遍历,执行它们的 _fireCompositionCallbacks 函数。

dart 复制代码
  @override
  void _fireCompositionCallbacks({required bool includeChildren}) {
    super._fireCompositionCallbacks(includeChildren: includeChildren);
    
    // 如果入参 includeChildren 为 true,即不包含自己的子级的话,直接 return。
    if (!includeChildren) {
      return;
    }
    
    // 遍历自己的子级 Layer,调用它们的 _fireCompositionCallbacks 函数,
    // 即沿着子级链表进行。
    Layer? child = firstChild;
    
    while (child != null) {
      // 执行子级的 _fireCompositionCallbacks 函数
      child._fireCompositionCallbacks(includeChildren: includeChildren);
      
      // 继续遍历下一个子级
      child = child.nextSibling;
    }
  }

firstChild & lastChild & hasChildren

firstChild 是此 ContainerLayer 子级列表中的第一个 Layer。lastChild 是此 ContainerLayer 子级列表中的最后一个 Layer。ContainerLayer 的子级列表其实是以一个双向链表的形式存在的,并且这里还用两个指针分别执行链表头和链表尾,后面看 append 函数时,会详细看到此双向链表的构建过程。

hasChildren getter,只要 _firstChild 不为 null,说明此 ContainerLayer 至少有一个子级 Layer。

dart 复制代码
  Layer? get firstChild => _firstChild;
  Layer? _firstChild;

  Layer? get lastChild => _lastChild;
  Layer? _lastChild;

  // 即只要 _firstChild 不为 null,说明此 ContainerLayer 至少有一个子级 Layer。 
  bool get hasChildren => _firstChild != null;

supportsRasterization

需要此 ContainerLayer 的所有子级 Layer 都支持光栅化,那么此 ContainerLayer 才支持光栅化。

这个 Layer 或任何子 Layer 是否可以使用 Scene.toImage 或 Scene.toImageSync 进行栅格化(栅格化的意思是是否可以转换为位图)。如果为 false,则调用上述方法可能会产生不完整的图像。

这个值可能会在 ContainerLayer 对象的生命周期中发生变化,因为它的子 Layer 本身会被添加或移除。

dart 复制代码
  @override
  bool supportsRasterization() {
    
    // 从 lastChild 往前遍历,只要有一个子图层不支持光栅化,那么此 ContainerLayer 就不支持光栅化。 
    for (Layer? child = lastChild; child != null; child = child.previousSibling) {
      
      // 只要 ContainerLayer 有一个 child 不支持光栅化,则直接返回 false。 
      if (!child.supportsRasterization()) {
        return false;
      }
    }
    
    // 都支持的话,那么此 ContainerLayer 也支持光栅化。
    return true;
  }

buildScene

把这个 ContainerLayer 当作根层,在 engine 中构建一个 Scene。

这个方法位于 ContainerLayer 类中,而不是 PipelineOwner 或其他单例级别,是因为这个方法既可以用于渲染整个 Layer Tree(例如普通应用程序帧),也可以用于渲染 Layer SubTree(例如 OffsetLayer.toImage)。

dart 复制代码
  ui.Scene buildScene(ui.SceneBuilder builder) {
    // 更新以当前 ContainerLayer 为根的整棵 Layer 子树的 _needsAddToScene 属性,
    // 且同我们之前学习的 RenderObject 的 needsCompositing 的更新方式类似,当子级为 true 时,父级也要为 true。
    // 这里则是子级 Layer 的 needsAddToScene 为 true 时,父级 Layer 的 needsAddToScene 也必为 true。 
    updateSubtreeNeedsAddToScene();
    
    // addToScene 是 Layer 的一个抽象函数,需要它的子类来实现。
    // 这里 ContainerLayer 对它进行了自己的实现,即执行所有子级的 addToScene 函数。
    addToScene(builder);
    
    // 如果有的话,执行所有子级的合成回调。
    if (subtreeHasCompositionCallbacks) {
      _fireCompositionCallbacks(includeChildren: true);
    }
    
    // 在调用 addToScene 之后再清除 needsAddToScene 标记,而不是在调用之前。
    // 这是因为 addToScene 会调用子节点的 addToScene 方法,这可能会将该 CoontainerLayer 的 needsAddToScene 标记为 true。
    _needsAddToScene = false;
    
    // 生成一个 scene
    final ui.Scene scene = builder.build();
    return scene;
  }

dispose

当 ContainerLayer 执行 dispose 时,需要先移除自己的所有子级 Layer,然后再清理自己的 callbacks。

dart 复制代码
  @override
  void dispose() {
    removeAllChildren();
    _callbacks.clear();
    
    super.dispose();
  }

updateSubtreeNeedsAddToScene

从这个 ContainerLayer 开始遍历整棵 Layer 子树,并确定是否需要 addToScene。

如果满足以下任一条件,则图层需要 addToScene:

  • alwaysNeedsAddToScene 为 true。
  • 已调用 markNeedsAddToScene。
  • 任何后代图层需要 addToScene。

ContainerLayer 重写此方法以递归地在其子级 Layer 上调用它。

dart 复制代码
  @override
  void updateSubtreeNeedsAddToScene() {
    super.updateSubtreeNeedsAddToScene();
    
    // 遍历所有的子级 Layer,并且如下子级 needsAddToScene 为 true,则父级也必为 true。
    Layer? child = firstChild; 
    
    while (child != null) {
      child.updateSubtreeNeedsAddToScene();
      
      // 子级 _needsAddToScene 为 true,则自己的 _needsAddToScene 也必为 true
      _needsAddToScene = _needsAddToScene || child._needsAddToScene;
      
      child = child.nextSibling;
    }
  }

addChildrenToScene

将该 ContainerLayer 的所有子级 Layer 上传到 engine。

通常,此方法由 addToScene 使用,以将子级 Layer 插入到 Scene 中。ContainerLayer 的子类通常会重写 addToScene 方法,利用 SceneBuilder API 对 Scene 应用效果,然后使用 addChildrenToScene 将它们的子级 Layer 插入到 Scene 中,在从 addToScene 返回之前撤销上述效果。

dart 复制代码
  void addChildrenToScene(ui.SceneBuilder builder) {
    Layer? child = firstChild;
    
    // 沿着 firstChild 遍历所有的子级,调用它们的 _addToSceneWithRetainedRendering 函数。
    while (child != null) {
      child._addToSceneWithRetainedRendering(builder);
      
      child = child.nextSibling;
    }
  }

attach

将这个 ContainerLayer 标记为已附加到给定的 owner。

具有子级的 Layer 子类应该重写此方法,在调用继承方法 super.attach(owner) 后将所有子级连接到相同的 owner 上。

dart 复制代码
  @override
  void attach(Object owner) {
    super.attach(owner);
    
    Layer? child = firstChild;
    // 遍历整个 child 链递归调用。
    while (child != null) {
      child.attach(owner);
      
      child = child.nextSibling;
    }
  }

detach

将该 ContainerLayer 标记为与其 owner 脱离关系。

具有子级的 Layer 子类应该重写此方法,在调用继承方法 super.detach() 后将所有子级从 owner 脱离。

dart 复制代码
  @override
  void detach() {
    super.detach();
    
    // 沿着 child 链递归调用子级的 detach 函数
    Layer? child = firstChild;
    while (child != null) {
      child.detach();
      child = child.nextSibling;
    }
    
    // 并且要回调自己的 _fireCompositionCallbacks,includeChildren 入参为 false,
    // 表示只调用自己的。
    _fireCompositionCallbacks(includeChildren: false);
  }

_adoptChild

_adoptChild 作为把入参 child 添加作为当前 ContainterLayer 的子级所使用的一个私有函数,主要的功能是为入参 child 的各个属性赋值。

dart 复制代码
  void _adoptChild(Layer child) {
    // 如果当前 ContainerLayer 的 alwaysNeedsAddToScene 值不为 false,
    // 则需要把它标记为需要进行 addToScene。(因为它添加了新的子级,所以肯定要进行新的 addToScene,生成新的位图!)
    if (!alwaysNeedsAddToScene) {
      markNeedsAddToScene();
    }
    
    // 如果入参 child 的合成回调的数量不等于 0,则需要更新当前这个 ContainerLayer 的合成回调的数量,
    // 即在原有的合成回调的数量上,再加上当前这个入参 child 的合成回调的数量。
    if (child._compositionCallbackCount != 0) {
      _updateSubtreeCompositionObserverCount(child._compositionCallbackCount);
    }
    
    // 把入参 child 的 parent 指针,执行当前的 ContainerLayer
    child._parent = this;
    
    // 如果当前 ContainerLayer 的 attached 为 true,即它的 owner 不为 null,
    // 则也更新入参 child 的 owner 为此 owner 属性。
    if (attached) {
      child.attach(_owner!);
    }
    
    // 然后更新以入参 child 为根节点的,整个 Layer 子树上各个节点的 depth 值。
    redepthChild(child);
  }

append

将给定的入参 Layer child 添加到该 ContainerLayer 的子级列表的末尾。

还记得 PaintingContext 的 appendLayer 函数吗?内部就是直接调用 PaintingContext 的 _containerLayer 属性的 append 函数,正是使用此函数完成新的 Layer 层的添加,既而进行 Layer Tree 的构建。

在学习 PaintingContext 的 appendLayer 函数时并没有深入学习 append 函数,下面看一下它的具体实现内容。

dart 复制代码
  void append(Layer child) {
    // 把入参 child 添加作为当前 ContainerLayer 的子级。
    _adoptChild(child);
    
    // 然后,下面主要是更新当前 ContainerLayer 的 _lastChild 和 _firstChild 两个属性的值,
    // 以及入参 child 的 _previousSibling 属性,把它与它的兄弟节点连接起来。
    //(ContainerLayer 子级列表中的 Layer 节点,它们兄弟节点是一个双向链表。)
    
    child._previousSibling = lastChild;
    if (lastChild != null) {
      lastChild!._nextSibling = child;
    }
    
    _lastChild = child;
    _firstChild ??= child;
    child._parentHandle.layer = child;
  }

redepthChildren & redepthChild

redepthChildren:如果该节点有子级节点,请调整子级节点的深度信息:_depth。在具有子级节点的 Layer 子类中重写此方法,对每个子级节点调用 ContainerLayer.redepthChild,请勿直接调用此方法。

redepthChild:调整给定子级节点的深度,使其大于此节点自身的深度。只能在重写 redepthChildren 方法时调用此方法。

dart 复制代码
  @override
  void redepthChildren() {
    // 沿着 firstChild 和 nextSibling 更新节点的 depth 信息。
    Layer? child = firstChild;
    while (child != null) {
      redepthChild(child);
      child = child.nextSibling;
    }
  }

  @protected
  void redepthChild(Layer child) {
    // 如果入参 child 的 depth 小于当前的它的直接父级的 depth 的话,
    // 那肯定要更新它的 depth 的值,更新为当前它的直接父级的 depth + 1。
    if (child._depth <= _depth) {
    
      // 更新为直接父级的 depth + 1
      child._depth = _depth + 1;
      
      // 然后同样的要更新自己的子级的 depth。
      child.redepthChildren();
    }
  }

_removeChild & _dropChild

在 Layer.remove 函数中使用到的函数,从父级的子级列表中移除指定的某个子级。

dart 复制代码
  void _removeChild(Layer child) {
  
    // 首先更新当前 ContainerLayer 的子级列表的双向链表,把 child 从此链表中移除。 
    
    // 判断入参 child 的 _previousSibling 是否 null,更新 _firstChild 指向。
    if (child._previousSibling == null) {
      // 如果入参 child 的 _previousSibling 为 null,则说明它是当前 ContainerLayer 的 _firstChild 指向,
      // 则更新 _firstChild 为 child 的 _nextSibling,即 child 的后一个兄弟节点作为当前 ContainerLayer 的 _firstChild。
      _firstChild = child._nextSibling;
    } else {
      // 否则的话,则更新 child 的前向节点的 next 节点为 child 的 next 节点。
      child._previousSibling!._nextSibling = child.nextSibling;
    }
    
    // 判断入参 child 的 _nextSibling 是否为 null,更新 _lastChild 指向。
    if (child._nextSibling == null) {
      // 如果入参 child 的 _nextSibling 为 null,则说明它是当前 ContainerLayer 的 _lastChild 指向,
      // 则更新 _lastChild 为 child 的 previousSibling,即 child 的前一个兄弟节点作为当前 ContainerLayer 的 _lastChild。
      _lastChild = child.previousSibling;
    } else {
      // 否则的话,则更新 child 的后向节点的 "前向" 指向为 child 的 "前向" 节点。
      child.nextSibling!._previousSibling = child.previousSibling;
    }
    
    // 然后是清理 child 的内容:前向和后向都指向 null,其它 Layer Tree 相关的属性也置为 null,
    // parentHandle.layer 也置为 null!
    child._previousSibling = null;
    child._nextSibling = null;
    _dropChild(child);
    child._parentHandle.layer = null;
  }
  
  void _dropChild(Layer child) {
    // 如果当前的 ContainerLayer 的 alwaysNeedsAddToScene 为 false,
    // 则需要此 ContainerLayer 进行 addToScene 操作。
    if (!alwaysNeedsAddToScene) {
      markNeedsAddToScene();
    }
    
    // 此 child 的合成回调数量不是 0 的话,则需要更新自己的父级的合成回调的数量,减去这个值。
    if (child._compositionCallbackCount != 0) {
      // 注意这里传入的是负值:-child._compositionCallbackCount
      _updateSubtreeCompositionObserverCount(-child._compositionCallbackCount);
    }
    
    // parent 指向置为 null
    child._parent = null;
    
    // detach 执行,即 owner 置为 null。
    if (attached) {
      child.detach();
    }
  }

removeAllChildren

将此 ContainerLayer 的所有子级节点从其子级列表中全部移除。

dart 复制代码
  void removeAllChildren() {
    Layer? child = firstChild;
    
    // 沿着子级的链表进行移除,无甚复杂的,可以帮助大家熟悉熟悉链表的操作。
    while (child != null) {
      final Layer? next = child.nextSibling;
      child._previousSibling = null;
      child._nextSibling = null;
      
      _dropChild(child);
      
      child._parentHandle.layer = null;
      child = next;
    }
    
    _firstChild = null;
    _lastChild = null;
  }

addToScene

重写此方法以将此 ContainerLayer 上传到 engine。addToScene 是 Layer 的一个抽象函数,需要由 Layer 的子类来实现。而 ContainerLayer 的 addToScene 函数内部则是仅调用:addChildrenToScene 函数,所以下面我们直接看 addChildrenToScene 函数。

dart 复制代码
  @override
  void addToScene(ui.SceneBuilder builder) {
    addChildrenToScene(builder);
  }

addChildrenToScene

将这个 ContainerLayer 的所有子级上传到 engine 中。

通常,此方法由 addToScene 使用,以将子级插入 Scene 中。ContainerLayer 的子类通常会重写 addToScene 方法,使用 SceneBuilder API 应用场景效果,然后使用 addChildrenToScene 方法插入它们的子级,最后在返回 addToScene 之前撤销前述效果。

dart 复制代码
  void addChildrenToScene(ui.SceneBuilder builder) {
    Layer? child = firstChild;
    
    // 沿着子级链表调用,所有子级的 _addToSceneWithRetainedRendering 函数。
    while (child != null) {
      child._addToSceneWithRetainedRendering(builder);
      child = child.nextSibling;
    }
  }

applyTransform

入参是 child 和 transform。将应用于给定矩阵的变换(Matrix4 transform),该 transform 在对给定子级 child 进行合成时会应用。

具体来说,这应用于子级原点的 transform。当在一系列 Layer 中使用 applyTransform 时,除非链中最深的 Layer 将图层偏移(layerOffset) 折叠为零,否则结果将不可靠。这意味着它向其子级传递 Offset.zero,并将任何传入的图层偏移(bakes any incoming layerOffset) 到 SceneBuilder 中作为(例如)一个 transform,然后这个 transform 也包含在 applyTransform 应用的 transform 中。

例如,如��� addToScene 应用了 layerOffset,然后将 Offset.zero 传递给子级,那么它应该包含在此处应用的 transform 中;而如果 addToScene 只是将 layerOffset 传递给子级,那么它就不应该包含在此处应用的 transform 中。

这个方法只在 addToScene 被调用后立即有效,在任何属性被更改之前。

默认实现什么也不做,因为默认情况下,ContainerLayer 在 ContainerLayer 本身的原点上合成其子级。(ContainerLayer 的此函数默认实现是空的。)

通常情况下,child 参数不应为 null,因为原则上每个 Layer 都可以独立地对每个子级进行 transform。然而,某些 Layer 可能会明确允许 null 作为值,例如,如果它们知道它们会以相同的方式转换所有子级。

被 FollowerLayer 使用,以将其子级转换为 LeaderLayer 的位置。(学习 FollowerLayer 时再深入学习。)

dart 复制代码
  void applyTransform(Layer? child, Matrix4 transform) {
    // 
  }

depthFirstIterateChildren

以深度优先顺序返回该 ContainerLayer 的子级 Layer。

dart 复制代码
  @visibleForTesting
  List<Layer> depthFirstIterateChildren() {
    // 如果 firstChild 为 null,说明当前没有子级,则直接返回空:<Layer>[]。
    if (firstChild == null) {
      return <Layer>[];
    }
    
    // 准备一个 List
    final List<Layer> children = <Layer>[];
    
    Layer? child = firstChild;
    
    // 沿着子级链表进行遍历,返回整个 Layer 子树上的所有 Layer。
    while (child != null) {
      children.add(child);
      
      // 如果此 child 是一个 ContainerLayer,则遍历返回它的所有子级。
      if (child is ContainerLayer) {
        children.addAll(child.depthFirstIterateChildren());
      }
      
      child = child.nextSibling;
    }
    
    return children;
  }

ContainerLayer 总结

只有 ContainerLayer 的子类可以在 Layer Tree 中拥有子级,所有其他 Layer 类都用作 Layer Tree 中的叶子节点。在 ContainerLayer 中继承自 Layer 的 nextSibling 和 previousSibling 两个属性发挥了自己的作用:ContainerLayer 的子级会通过这两个指针构建 ContainerLayer 的子级列表的双向链表结构,然后 ContainerLayer 还通过 firstChild 和 lastChild 属性来记录 ContainerLayer 的子级链表的头和尾,然后在加上 Layer 的 parent 指针,以及 ContainerLayer 的 append 函数,正是这些属性和函数为 Layer Tree 的树形结构奠定了代码基础。

ContainerLayer 让我们看到了 Layer Tree 中 Layer 节点的拼接过程,后面还有更多关于叶子节点的内容,以及 Scene 的内容,ContainerLayer 中的 ui.Scene buildScene(ui.SceneBuilder builder) 函数让我们更加接近 SceneBuilder 和 Scene 本质。我们下篇继续!

参考链接

参考链接:🔗

相关推荐
m0_7482478015 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
迷雾漫步者17 小时前
Flutter组件————PageView
flutter·跨平台·dart
迷雾漫步者1 天前
Flutter组件————FloatingActionButton
前端·flutter·dart
coder_pig1 天前
📝小记:Ubuntu 部署 Jenkins 打包 Flutter APK
flutter·ubuntu·jenkins
捡芝麻丢西瓜1 天前
flutter自学笔记5- dart 编码规范
flutter·dart
恋猫de小郭1 天前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
sunly_2 天前
Flutter:导航,tab切换,顶部固定,列表分页滚动
开发语言·javascript·flutter
敲代码的小强2 天前
Flutter项目兼容鸿蒙Next系统
flutter·华为·harmonyos
Zh-jie3 天前
flutter 快速实现侧边栏
前端·javascript·flutter
truemi.733 天前
flutter --no-color pub get 超时解决方法
android·flutter