写点什么

【Flutter 专题】102 何为 Flutter RenderObjects ?

发布于: 2021 年 06 月 17 日
【Flutter 专题】102 何为 Flutter RenderObjects ?

      小菜前段时间简单了解了一下 WidgetElement,其中 Widget 主要是存放渲染内容以及布局信息等,仅作为一个信息存储的容器;Element 主要用于存放上下文环境,遍历 UI View 视图树;而小菜今天尝试学习的 RenderObject 才是 UI View 真正的渲染部分;

RenderObject

      RenderObject 作为渲染树中的一个对象;其 layout()paint() 是渲染库核心,负责管理布局和渲染等;RenderObject 定义了布局绘制协议,但并没定义具体布局绘制模型;

源码分析

      RenderObject 可以从多个维度研究,可以通过 layout()paint() 对比 Android 的绘制流程,也可以根据其属性和交互的对象(parent / owner / child)来学习;小菜从头开始为了尽可能多的了解源码,尝试第二种方式进一步学习;


abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {    AbstractNode _rootNode;    ParentData parentData;    Constraints _constraints;    @protected    Constraints get constraints => _constraints;    PipelineOwner _owner;    bool get attached => _owner != null;    void setupParentData(covariant RenderObject child) {}    void adoptChild(RenderObject child) {}    void dropChild(RenderObject child) {}    void attach(PipelineOwner owner) {}    void detach() {}}
复制代码

parent 相关

1. ParentData
ParentData parentData;
void setupParentData(covariant RenderObject child) { assert(_debugCanPerformMutations); if (child.parentData is! ParentData) child.parentData = ParentData();}
复制代码


      RenderObject 包括两个重要属性 parentParentData 插槽;ParentData 做为一个预留的变量,由 parent 赋值,传递信息给 child 的存储容器;通常所有和 child 特定的数据都可以存储在 ParentData 中;

2. Constraints
void layout(Constraints constraints, { bool parentUsesSize = false }) {    RenderObject relayoutBoundary;    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {      relayoutBoundary = this;    } else {      final RenderObject parent = this.parent;      relayoutBoundary = parent._relayoutBoundary;    }       if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) return;    _constraints = constraints;    if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {      visitChildren((RenderObject child) {        child._cleanRelayoutBoundary();      });    }    _relayoutBoundary = relayoutBoundary;        if (sizedByParent) {      try {        performResize();      } catch (e, stack) {        _debugReportException('performResize', e, stack);      }    }    RenderObject debugPreviousActiveLayout;    try {      performLayout();      markNeedsSemanticsUpdate();    } catch (e, stack) {      _debugReportException('performLayout', e, stack);    }    _needsLayout = false;    markNeedsPaint();}
复制代码


      Constraints 作为 RenderObjectparentchild 之间的布局约束;layout() 作为 RenderObject 的核心方法,需要传入 Constraints 作为约束,配合 parentUsesSize 判断 RenderObjectchild 子节点发生变化时,parent 父节点是否需要重新绘制;

3. relayoutBoundary
RenderObject relayoutBoundary;if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {  relayoutBoundary = this;} else {  final RenderObject parent = this.parent;  relayoutBoundary = parent._relayoutBoundary;}if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {  visitChildren((RenderObject child) {    child._cleanRelayoutBoundary();  });}
void markNeedsLayout() { if (_needsLayout) { return; } if (_relayoutBoundary != this) { markParentNeedsLayout(); } else { _needsLayout = true; if (owner != null) { owner._nodesNeedingLayout.add(this); owner.requestVisualUpdate(); } }}
复制代码


      layout() 中定义了一个 RenderObject 类型的 relayoutBoundary 布局边界,如果布局边界发生变化,则遍历清空所有已记录的边界并重新设置;


      markNeedsLayout() 中也需要进行布局边界判断,若 RenderObject 自身不是 relayoutBoundary,则向 parent 父节点查找,直到找到确定是 relayoutBoundaryRenderObject 并标记为 dirty


      layout() 确定自己是否为边界需要判断四个条件,分别是 !parentUsesSize parent 父节点是否关心自己的大小;sizedByParent 是否由 parent 父节点判断大小;constraints.isTight 是否严格约束;parent is! RenderObject 自身是否为 root 根节点;

owner 相关

      PipelineOwner 作为整个渲染流程的管理者;提供用于驱动渲染管道的接口,并存储在管道的每个阶段中已请求访问渲染对象的状态等;

1. flushLayout
void flushLayout() {    if (!kReleaseMode) {      Timeline.startSync('Layout', arguments: timelineWhitelistArguments);    }    try {      while (_nodesNeedingLayout.isNotEmpty) {        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;        _nodesNeedingLayout = <RenderObject>[];        for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {          if (node._needsLayout && node.owner == this)            node._layoutWithoutResize();        }      }    } finally {      if (!kReleaseMode)  Timeline.finishSync();    }}
复制代码


      flushLayout() 用于遍历所有标记为 dirty 的需要重新布局的 RenderObjects 并重新计算其布局尺寸和位置等;

2. flushCompositingBits
void flushCompositingBits() {    if (!kReleaseMode) {      Timeline.startSync('Compositing bits');    }    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);    for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {      if (node._needsCompositingBitsUpdate && node.owner == this)        node._updateCompositingBits();    }    _nodesNeedingCompositingBitsUpdate.clear();    if (!kReleaseMode) {      Timeline.finishSync();    }}
复制代码


      flushCompositingBits() 用于遍历所有标记为 dirty 的需要 CompositingBitsUpdate 合并更新的子节点,再次阶段,每个 RenderObject 都会了解其子节点是否需要合并更新;

3. flushPaint
void flushPaint() {    if (!kReleaseMode) {      Timeline.startSync('Paint', arguments: timelineWhitelistArguments);    }    try {      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;      _nodesNeedingPaint = <RenderObject>[];      for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {        if (node._needsPaint && node.owner == this) {          if (node._layer.attached) {            PaintingContext.repaintCompositedChild(node);          } else { node._skippedPaintingOnLayer(); }        }      }    } finally {      if (!kReleaseMode) { Timeline.finishSync(); }    }}
复制代码


      flushPaint() 用于遍历所有标记为 dirty 的需要重新绘制的子节点,并生成 Layer 用于绘制展示;

4. attach / detach
@overridevoid attach(PipelineOwner owner) {    super.attach(owner);    if (_needsLayout && _relayoutBoundary != null) {      _needsLayout = false;      markNeedsLayout();    }    if (_needsCompositingBitsUpdate) {      _needsCompositingBitsUpdate = false;      markNeedsCompositingBitsUpdate();    }    if (_needsPaint && _layer != null) {      _needsPaint = false;      markNeedsPaint();    }    if (_needsSemanticsUpdate && _semanticsConfiguration.isSemanticBoundary) {      _needsSemanticsUpdate = false;      markNeedsSemanticsUpdate();    }}
复制代码


      layout() 中在 attach()detach() 中也需要 PipelineOwnerattach() 主要通知管理者 owner 将其插入到渲染树中标记需要计算布局 layout 并调用 markNeedsPaint 重新绘制;detach() 主要是通知管理者取消关联;

child 相关

      对于 child 子节点,小菜主要学习如下三个方法;

1. adoptChild
@overridevoid adoptChild(RenderObject child) {    setupParentData(child);    markNeedsLayout();    markNeedsCompositingBitsUpdate();    markNeedsSemanticsUpdate();    super.adoptChild(child);}
复制代码


      adoptChild() 主要是 RenderObject 添加一个 child 子节点;其中需要通过 setupParentData() 来获取 ParentData 中的数据并更新;

2. dropChild
@overridevoid dropChild(RenderObject child) {    child._cleanRelayoutBoundary();    child.parentData.detach();    child.parentData = null;    super.dropChild(child);    markNeedsLayout();    markNeedsCompositingBitsUpdate();    markNeedsSemanticsUpdate();}
复制代码


      dropChild() 是和 adoptChild() 对应的方法,主要用于 RenderObject 删除一个 child 子节点;删除过程中需要 _cleanRelayoutBoundary 清除边界并删除 ParentData,之后再更新;

3. paintChild
void paintChild(RenderObject child, Offset offset) {    if (child.isRepaintBoundary) {      stopRecordingIfNeeded();      _compositeChild(child, offset);    } else {      child._paintWithContext(this, offset);    }}
复制代码


      paintChild() 为绘制一个子节点的 RenderObject;如果该子节点有自己合成层,则 child 子节点将被合成到与此绘制相关的上下文相关的 Layer 层中;

RenderBox

      RenderObject 并没定义具体布局绘制模型,所以小菜简单学习了一下 RenderBoxRenderBoxRenderObject 的子类,以屏幕左上角为原点(包括顶部状态栏)坐标系;BoxParentData 作为 child 子节点传输数据,BoxConstraints 作为其约束条件,通过 Size 记录其尺寸大小;可以定义具体的布局绘制模型;




      RenderObject 涉及的方式方法较多,小菜对于源码的理解还不够深入,如有错误,请多多指导!


来源: 阿策小和尚

发布于: 2021 年 06 月 17 日阅读数: 6
用户头像

还未添加个人签名 2021.05.13 加入

Android / Flutter 小菜鸟~

评论

发布
暂无评论
【Flutter 专题】102 何为 Flutter RenderObjects ?