從源碼看flutter系列集合node
上一篇 從源碼看flutter(二):Element篇 咱們經過 Element
的生命週期對 Element
有了一個總體的瞭解,此次咱們未來對 RenderObject
作深刻的分析,進一步完善咱們對於 flutter framework的瞭解git
在第一篇,咱們知道 RenderObject
的建立方法是由 RenderObjectWidget
提供的,而咱們已經瞭解過了 Element
的生命週期,這裏,咱們將選擇 RenderObjectElement
做爲此篇文章的分析起點canvas
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
方法,若是有子類重寫它,則重寫方法中的參數類型須要爲 RenderObject
或 RenderObject
的子類。具體的說明,能夠看這裏ide
這裏簡單的對 RenderObjectElement
作了一個介紹,接下來,咱們就來看一下它在初始化方法 mount(...)
中都作了寫什麼佈局
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
...
_renderObject = widget.createRenderObject(this);
...
assert(_slot == newSlot);
attachRenderObject(newSlot);
_dirty = false;
}
複製代碼
能夠看到,在 mount(...)
方法中,調用了 RenderObjectWidget
的 createRenderObject(...)
方法建立了 RenderObject
對象。學習
以後經過 attachRenderObject(newSlot)
對 RenderObject
進行了進一步的操做測試
@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
存儲在子節點中時纔會用到,好比 Stack
和 Position
,其中 Position
對應的 Element
就是 ParentDataElement
能夠簡單看一下 _findAncestorRenderObjectElement()
邏輯
RenderObjectElement _findAncestorRenderObjectElement() {
Element ancestor = _parent;
while (ancestor != null && ancestor is! RenderObjectElement)
ancestor = ancestor._parent;
return ancestor as RenderObjectElement;
}
複製代碼
就是簡單的遍歷父節點,當結點爲null或者是 RenderObjectElement
時就會退出遍歷。因此這裏找到的是最近的祖先結點
以後會調用 RenderObjectElement
的 insertChildRenderObject(...)
將child插入,這是一個抽象方法,具體的邏輯都交由子類去實現
咱們知道,比較經常使用的兩個 RenderObjectElement
的實現類,分別是 SingleChildRenderObjectElement
和 MultiChildRenderObjectElement
。前者表示只有一個子節點,常見的對應 Widget
有 Padding
、 Align
、SizeBox
等 ;後者表示有多個子節點,常見對應的 Widget
有 Wrap
、Stack
、Viewport
等
每一個 RenderObjectElement
的實現類,其 insertChildRenderObject(...)
都有所不一樣,但最終都會調用到 RenderObject
的 adoptChild(child)
方法
接下來,咱們進入到本篇文章主角 RenderObject
的相關信息
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
...
}
複製代碼
能夠看到, RenderObject
是 AbstractNode
的子類,而且實現了 HitTestTarget
接口, HitTestTarget
是用於處理點擊事件的
咱們來看一下 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
的實現
@override
void adoptChild(RenderObject child) {
...
setupParentData(child);
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
super.adoptChild(child);
}
複製代碼
這裏,咱們主要關注 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
的刷新流程,會走到 WidgetsBinding
的 drawFrame()
方法
@override
void drawFrame() {
...
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
}
...
}
複製代碼
在 buildScope(...)
和 finalizeTree()
中間,調用了 super.drawFrame()
,它會來到 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()
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()
方法
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()
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
進行判斷作了不一樣的邏輯處理,若是 RenderObject
的 isRepaintBoundary
不爲 true 則會一直找向父節點查找,直到找到 true 爲止,而後將它們一塊兒繪製,因此合理重寫這個方法能夠避免沒必要要的繪製。
當 isRepaintBoundary
爲 true 時,就是將須要繪製的 RenderObject
放入 PipelineOwner
維護的另外一個列表 _nodesNeedingPaint
中
PipelineOwner
的 flushLayout()
差很少就結束了,接下來看一下它的 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
來繪製的
class PaintingContext extends ClipContext {
...
static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) {
assert(child._needsPaint);
_repaintCompositedChild(
child,
debugAlsoPaintedParent: debugAlsoPaintedParent,
);
}
...
}
複製代碼
它調用了 _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(...)
中
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
並將數據存入其中。好比 RenderAligningShiftedBox
在 performLayout()
時,就會給 child 設置 BoxParentData
,在其中存儲偏移量 Offeset
,表明 Widgett
就是 Center
RenderObject
在進行繪製時,會判斷當前的 isRepaintBoundary
是否爲 true,是則建立一個本身的 Layer
去進行進行繪製,默認爲 OffsetLayer
;不是則從父節點中獲取 Layer
,與父節點一塊兒繪製。關於 Layer
的更多信息,將在下一篇中詳細說明