從源碼看flutter(三):RenderObject篇

從源碼看flutter系列集合node

開篇

上一篇 從源碼看flutter(二):Element篇 咱們經過 Element 的生命週期對 Element 有了一個總體的瞭解,此次咱們未來對 RenderObject 作深刻的分析,進一步完善咱們對於 flutter framework的瞭解git

第一篇,咱們知道 RenderObject 的建立方法是由 RenderObjectWidget 提供的,而咱們已經瞭解過了 Element 的生命週期,這裏,咱們將選擇 RenderObjectElement 做爲此篇文章的分析起點canvas

RenderObjectElement

RenderObjectElement 與以前咱們分析的其餘 Element 對象差異不是特別大,主要的不一樣點在下面的幾個方法中bash

abstract class RenderObjectElement extends Element {
  ...
  @override
  void attachRenderObject(dynamic newSlot) { ... }
  
  @override
  void detachRenderObject() { ... }
  
  @protected
  void insertChildRenderObject(covariant RenderObject child, covariant dynamic slot);
  
  @protected
  void moveChildRenderObject(covariant RenderObject child, covariant dynamic slot);
  
  @protected
  void removeChildRenderObject(covariant RenderObject child);

}
複製代碼

分別是實現了 Element 提供的 attachRenderObject(newSlot)detachRenderObject(),並提供給子類後面三個方法去對 RenderObject 對象進行相關操做。dom

這裏能夠看到,有的參數被關鍵字 covariant 所修飾,這個關鍵字是用於對重寫方法的參數作限制的,像上面的 removeChildRenderObject 方法,若是有子類重寫它,則重寫方法中的參數類型須要爲 RenderObjectRenderObject 的子類。具體的說明,能夠看這裏ide

這裏簡單的對 RenderObjectElement 作了一個介紹,接下來,咱們就來看一下它在初始化方法 mount(...) 中都作了寫什麼佈局

mount(...)

@override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    ...
    _renderObject = widget.createRenderObject(this);
    ...
    assert(_slot == newSlot);
    attachRenderObject(newSlot);
    _dirty = false;
  }
複製代碼

能夠看到,在 mount(...) 方法中,調用了 RenderObjectWidgetcreateRenderObject(...) 方法建立了 RenderObject 對象。學習

以後經過 attachRenderObject(newSlot)RenderObject 進行了進一步的操做測試

attachRenderObject(newSlot)

@override
  void attachRenderObject(dynamic newSlot) {
    assert(_ancestorRenderObjectElement == null);
    _slot = newSlot;
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
    final ParentDataElement<ParentData> parentDataElement = _findAncestorParentDataElement();
    if (parentDataElement != null)
      _updateParentData(parentDataElement.widget);
  }
複製代碼

能夠看到,在 attachRenderObject(newSlot) 中,首先是尋找到祖先 RenderObjectElement 結點,而後將當前 RenderObject 插入其中。ui

後面還會查找 ParentDataElement,關於這類 Element ,是當父節點想要把數據經過 ParentData 存儲在子節點中時纔會用到,好比 StackPosition,其中 Position 對應的 Element 就是 ParentDataElement

能夠簡單看一下 _findAncestorRenderObjectElement() 邏輯

RenderObjectElement _findAncestorRenderObjectElement() {
    Element ancestor = _parent;
    while (ancestor != null && ancestor is! RenderObjectElement)
      ancestor = ancestor._parent;
    return ancestor as RenderObjectElement;
  }
複製代碼

就是簡單的遍歷父節點,當結點爲null或者是 RenderObjectElement 時就會退出遍歷。因此這裏找到的是最近的祖先結點

以後會調用 RenderObjectElementinsertChildRenderObject(...) 將child插入,這是一個抽象方法,具體的邏輯都交由子類去實現

咱們知道,比較經常使用的兩個 RenderObjectElement 的實現類,分別是 SingleChildRenderObjectElementMultiChildRenderObjectElement。前者表示只有一個子節點,常見的對應 WidgetPaddingAlignSizeBox 等 ;後者表示有多個子節點,常見對應的 WidgetWrapStackViewport

每一個 RenderObjectElement 的實現類,其 insertChildRenderObject(...) 都有所不一樣,但最終都會調用到 RenderObjectadoptChild(child) 方法

接下來,咱們進入到本篇文章主角 RenderObject 的相關信息

RenderObject

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
    ...
}
複製代碼

能夠看到, RenderObjectAbstractNode 的子類,而且實現了 HitTestTarget 接口, HitTestTarget 是用於處理點擊事件的

咱們來看一下 AbstractNode

AbstractNode

class AbstractNode {
  int _depth = 0;
  
  @protected
  void redepthChild(AbstractNode child) { ... }
  
  @mustCallSuper
  void attach(covariant Object owner) { ... }
  
  ...
  
  AbstractNode _parent;
  
  @protected
  @mustCallSuper
  void adoptChild(covariant AbstractNode child) { ... }
  
  @protected
  @mustCallSuper
  void dropChild(covariant AbstractNode child) { ... }
}
複製代碼

AbstractNode 中,提供了許多方法供子類實現,其中比較核心的就是 adoptChild(...)

能夠先簡單看一下它的 adoptChild(...)

@protected
  @mustCallSuper
  void adoptChild(covariant AbstractNode child) {
    ...
    child._parent = this;
    if (attached)
      child.attach(_owner);
    redepthChild(child);
  }
複製代碼

接下來,來看一下 RenderObject 的實現

adoptChild(...)

@override
  void adoptChild(RenderObject child) {
    ...
    setupParentData(child);
    markNeedsLayout();
    markNeedsCompositingBitsUpdate();
    markNeedsSemanticsUpdate();
    super.adoptChild(child);
  }
複製代碼

這裏,咱們主要關注 markNeedsLayout()

markNeedsLayout()

RenderObject _relayoutBoundary;
  ...
  @override
  PipelineOwner get owner => super.owner as PipelineOwner;
  ...

  void markNeedsLayout() {
    ...
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        ...
        owner._nodesNeedingLayout.add(this);
        owner.requestVisualUpdate();
      }
    }
  }
複製代碼

其中 _relayoutBoundary 表示從新佈局的邊界,若是當前 RenderObject 就是該邊界,則只須要將當前的 _needsLayout 設爲 true,同時將當前 RenderObject 添加到 PipelineOwner 中維護的 _nodesNeedingLayout 中;若是邊界是父節點的話,則會調用父節點的 markNeedsLayout() 方法

那麼下一步,咱們應該走到哪裏呢?

在上一篇中,咱們知道 Element 的刷新流程,會走到 WidgetsBindingdrawFrame() 方法

@override
  void drawFrame() {
    ...
    try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    }
    ...
  }
複製代碼

buildScope(...)finalizeTree() 中間,調用了 super.drawFrame(),它會來到 RendererBindingdrawFrame() 方法中

RendererBinding -> drawFrame()

@protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      renderView.compositeFrame(); // this sends the bits to the GPU
      pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
      _firstFrameSent = true;
    }
  }
複製代碼

能夠看到,這裏主要是經過 PipelineOwner 去進行一些操做,其中,咱們主要關注 flushLayout()flushPaint()

PipelineOwner

flushLayout()

List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
  ...
  void flushLayout() {
    ...
    try {
      ...
      while (_nodesNeedingLayout.isNotEmpty) {
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
          if (node._needsLayout && node.owner == this)
            node._layoutWithoutResize();
        }
      }
    } finally {
      ...
        _debugDoingLayout = false;
      ...
    }
  }
複製代碼

flushLayout() 中,依舊是先根據深度,也就是父節點在前子節點在後的順序進行排序,而後遍歷調用 RenderObject_layoutWithoutResize() 方法

能夠簡單看一下 _layoutWithoutResize() 方法

RenderObject -> _layoutWithoutResize()

void _layoutWithoutResize() {
    ...
    RenderObject debugPreviousActiveLayout;
    ...
      performLayout();
    ...
    _needsLayout = false;
    markNeedsPaint();
  }
  ...
  @protected
  void performLayout();
複製代碼

_layoutWithoutResize() 中,會調用 performLayout() 方法,這個方法交由子類實現,通常實現它的子類中,會在這個方法內調用 performLayout()onLayout(...) 方法

咱們能夠簡單看一下 onLayout(...)

void layout(Constraints constraints, { bool parentUsesSize = false }) {
    ...
    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
    }
    ...
    _relayoutBoundary = relayoutBoundary;
    ...
    if (sizedByParent) {
      ...
        performResize();
      ...
    }
    ...
      performLayout();
    ...
    _needsLayout = false;
    markNeedsPaint();
  }
複製代碼

基本上,執行 performLayout() 後就能夠肯定佈局的大小了,以後,都會調用 markNeedsPaint()

RenderObject -> markNeedsPaint()

void markNeedsPaint() {
    ...
    _needsPaint = true;
    if (isRepaintBoundary) {
      ...
      if (owner != null) {
        owner._nodesNeedingPaint.add(this);
        owner.requestVisualUpdate();
      }
    } else if (parent is RenderObject) {
      final RenderObject parent = this.parent as RenderObject;
      parent.markNeedsPaint();
      ...
    } else {
      ...
      if (owner != null)
        owner.requestVisualUpdate();
    }
  }
  
  ...
  bool get isRepaintBoundary => false;
複製代碼

能夠看到,經過對 isRepaintBoundary 進行判斷作了不一樣的邏輯處理,若是 RenderObjectisRepaintBoundary 不爲 true 則會一直找向父節點查找,直到找到 true 爲止,而後將它們一塊兒繪製,因此合理重寫這個方法能夠避免沒必要要的繪製。

isRepaintBoundarytrue 時,就是將須要繪製的 RenderObject 放入 PipelineOwner 維護的另外一個列表 _nodesNeedingPaint

PipelineOwnerflushLayout() 差很少就結束了,接下來看一下它的 flushPaint()

flushPaint()

void flushPaint() {
    ...
      _debugDoingPaint = true;
    ...
    try {
      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
      _nodesNeedingPaint = <RenderObject>[];
      ...
      for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
        assert(node._layer != null);
        if (node._needsPaint && node.owner == this) {
          if (node._layer.attached) {
            PaintingContext.repaintCompositedChild(node);
          } else {
            node._skippedPaintingOnLayer();
          }
        }
      }
      ...
    } finally {
      ...
        _debugDoingPaint = false;
      ...
    }
  }
複製代碼

依舊是排序後,進行遍歷處理。這裏又引入了一個新的概念 Layer

node._layer.attached 爲true 時表示該 Layer 對象被添加到了 Layer 樹中。關於這個對象,咱們會在下篇文章中進行更加詳細的說明

這裏只須要關注 PaintingContext.repaintCompositedChild(node) 方法

在此以前,先簡單的說明一下,PaintingContext 對象就是用於進行繪製的地方,它持有一個 Canvas 對象,你在flutter中看到的全部頁面,基本上都是由 Canvas 來繪製的

PaintingContext -> repaintCompositedChild(node)

class PaintingContext extends ClipContext {
  ...
  static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) {
    assert(child._needsPaint);
    _repaintCompositedChild(
      child,
      debugAlsoPaintedParent: debugAlsoPaintedParent,
    );
  }
  ...
}
複製代碼

它調用了 _repaintCompositedChild(...) 方法

PaintingContext -> _repaintCompositedChild(...)

static void _repaintCompositedChild(
    RenderObject child, {
    bool debugAlsoPaintedParent = false,
    PaintingContext childContext,
  }) {
    assert(child.isRepaintBoundary);
    ...
    if (childLayer == null) {
      ...
      child._layer = childLayer = OffsetLayer();
    } else {
      ...
      childLayer.removeAllChildren();
    }
    ...
    childContext ??= PaintingContext(child._layer, child.paintBounds);
    child._paintWithContext(childContext, Offset.zero);
    ...
    assert(identical(childLayer, child._layer));
    childContext.stopRecordingIfNeeded();
  }
複製代碼

最後,主要的邏輯都在 RenderObject_paintWithContext(...)

RenderObject -> _paintWithContext(...)

void _paintWithContext(PaintingContext context, Offset offset) {
    ...
    _needsPaint = false;
    try {
      paint(context, offset);
      ...
    }
    ...
  }
  
  void paint(PaintingContext context, Offset offset) { }
複製代碼

最後執行的 paint(...) 方法,顯然交由子類去實現。

其實到這裏,關於 RenderObject 的流程分析就差很少了。銷燬的部分和咱們以前看的 Element 銷燬都是大同小異的,因此這裏不介紹了

下面,咱們能夠用一個簡單的例子來做爲此次 RenderObject 的學習

例子

import 'package:flutter/material.dart';
import 'dart:math';

void main() => runApp(MyWidget());

class MyWidget extends SingleChildRenderObjectWidget{
  @override
  RenderObject createRenderObject(BuildContext context) => MyRenderObject();
}

class MyElement extends SingleChildRenderObjectElement{
  MyElement(SingleChildRenderObjectWidget widget) : super(widget);
}

class MyRenderObject extends RenderBox{

  @override
  BoxConstraints get constraints => BoxConstraints(minHeight: 100.0, minWidth: 100.0);

  @override
  void performLayout() => size = Size(constraints.minWidth + Random().nextInt(200), constraints.minHeight + Random().nextInt(200));

  @override
  void paint(PaintingContext context, Offset offset) {
    Paint paint = Paint()..color = Colors.primaries[Random().nextInt(Colors.primaries.length)];
    context.canvas.drawRect(Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height), paint);
  }
}
複製代碼

例子能夠直接在這裏進行測試:dartpad.dev/

咱們重寫了 RenderBox ,它是 RenderObject 一個主要的抽象實現類

RenderBox 中獲取 BoxConstraints 的方法默認調用的是父節點的 constraints ,這裏爲了簡化例子,咱們將其重寫了。這個對象主要是用於對 RenderBox 的大小進行限制,這個限制的邏輯你是能夠自定義的

通常狀況下,咱們要自定義本身的 RenderObject ,都是重寫 RenderBox。要自定義 Element 則是重寫 SingleChildRenderObjectElement 或者 MultiChildRenderObjectElement

那麼這篇關於 RenderObject 的文章就到這裏結束了

總結

簡單的總結一下 RenderObject 的一些特性吧

  • RenderObject 的主要職責就是完成界面的佈局、測量與繪製
  • RenderObject 中有一個 ParentData 對象,若是有父節點須要將某些數據存儲在子節點,會給子節點設置 ParentData 並將數據存入其中。好比 RenderAligningShiftedBoxperformLayout() 時,就會給 child 設置 BoxParentData,在其中存儲偏移量 Offeset,表明 Widgett 就是 Center
  • RenderObject 在進行繪製時,會判斷當前的 isRepaintBoundary 是否爲 true,是則建立一個本身的 Layer 去進行進行繪製,默認爲 OffsetLayer;不是則從父節點中獲取 Layer ,與父節點一塊兒繪製。關於 Layer 的更多信息,將在下一篇中詳細說明
相關文章
相關標籤/搜索