2、Widget、Element、RenderObjectcanvas
4、build 流程分析app
8、composite 流程分析post
第六章節簡單介紹了 圖層,並分析了影響圖層建立的元素之一 needsCompositing, 這一章節會重點介紹整個繪製流程。this
(1) flushPaintspa
void flushPaint() {
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();
}
}
}
}
複製代碼
上述代碼主要是兩個步驟 給節點排序 遍歷 _nodesNeedingPaint 判斷 layer.attached, 若是爲爲true, 則調用 repaintCompositedChild(node) 去作繪製,不然調用node._skippedPaintingOnLayer
(2) markNeedsPaint
void markNeedsPaint() {
assert(owner == null || !owner.debugDoingPaint);
if (_needsPaint)
return;
_needsPaint = true;
if (isRepaintBoundary) {
//若是當前節點 isRepaintBoundary 爲 true, 則將該節點添加到 _nodesNeedingPaint結合中
if (owner != null) {
owner._nodesNeedingPaint.add(this);
owner.requestVisualUpdate();
}
} else if (parent is RenderObject) {
// 向上層層遍歷父節點,直至將最近 isRepaintBoundary 爲true 的父節點添加到 _nodesNeedingPaint
final RenderObject parent = this.parent;
parent.markNeedsPaint();
} else {
// 根節點
if (owner != null)
owner.requestVisualUpdate();
}
}
複製代碼
因而可知, 重繪節點集合中只有 isRepaintBoundary 爲 true 的 renderObject, 和 relayoutBoundary 相似,該屬性決定這個RenderObject 重繪時是否獨立於其父節點,若是該屬性值爲true ,則獨立繪製,反之則一塊兒繪製(那若是是普通節點沒有加入 dirtyNodes集合, 它們怎麼繪製的呢?)
(3) _repaintCompositedChild
static void _repaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
PaintingContext childContext,
}) {
OffsetLayer childLayer = child._layer;
//1
if (childLayer == null) {
child._layer = childLayer = OffsetLayer();
} else {
childLayer.removeAllChildren();
}
//2 繪製上下文
childContext ??= PaintingContext(child._layer, child.paintBounds);
//三、繪製
child._paintWithContext(childContext, Offset.zero);
childContext.stopRecordingIfNeeded();
}
複製代碼
註釋1: 會先判斷節點的 layer 是否爲null,若是是 null, 則爲改節點建立一個新的layer, 若是不爲空,則須要清空該layer上的全部孩子節點(因爲只有 isRepaintBoundary 的 renderObject 纔在 dirtyNodes 集合中,所以不是全部的 renderObject 都擁有一個 layer) 註釋2:初始化繪製的上下文 ,這個相似 canvas 上下文 註釋3:調用節點的 _paintWithContext 進行繪製
(4) _paintWithContext
void _paintWithContext(PaintingContext context, Offset offset) {
paint(context, offset);
}
複製代碼
這個方法其實簡單調用了 paint
(5) paint
paint 方法 是由具體的實現類去重寫覆蓋的, 如下面 xx 類爲例
@override
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChild(child, offset);
}
複製代碼
(6) paintChild
void paintChild(RenderObject child, Offset offset) {
if (child.isRepaintBoundary) {
_compositeChild(child, offset);
} else {
child._paintWithContext(this, offset);
}
}
複製代碼
繪製主要分爲兩種狀況: 第一種狀況:有繪製邊界,即 isRepaintBoundary 爲 true時,則從新生成圖層和重繪, 往下第(7)點 第二種狀況: 普通繪製,往下第 (17)點
(7)_compositeChild
void _compositeChild(RenderObject child, Offset offset) {
if (child._needsPaint) {
// 重繪該節點
repaintCompositedChild(child, debugAlsoPaintedParent: true);
}
// 新增圖層,並將該圖層 append 到 父圖層
final OffsetLayer childOffsetLayer = child._layer;
childOffsetLayer.offset = offset;
appendLayer(child._layer);
}
複製代碼
若是節點須要重繪,則繪製該節點,同時生成新的 layer, 並將新的 layer 加入 父圖層
(8) appendLayer 這個方法調用 containerLayer 的 append 方法,將新增的圖層加入父圖層
@protected
void appendLayer(Layer layer) {
layer.remove();
_containerLayer.append(layer);
}
複製代碼
(9) append 調用 adoptChild 方法設置新的 layer 的 owner 值,同時經過設置 _previousSibling 和 _nextSibling 將該圖層節點加入圖層鏈表
void append(Layer child) {
adoptChild(child);
child._previousSibling = lastChild;
if (lastChild != null)
lastChild._nextSibling = child;
_lastChild = child;
_firstChild ??= child;
}
複製代碼
(10) adoptChild
void adoptChild(AbstractNode child) {
child._parent = this;
if (attached)
child.attach(_owner); // owner 只是一個對象,整個子樹的各個節點應該擁有相同的 owner, owner
}
複製代碼
(11) child.attach
@mustCallSuper
void attach(covariant Object owner) {
_owner = owner;
}
複製代碼
由上面可知, 在添加新的 layer 時,會調用子 layer 的 attach 方法將父 layer 的 owner 傳給子 layer
那何時會重置 layer 的 owner 值呢? 在移除 子 layer 的時候
(12) removeAllChildren 在 _repaintCompositedChild 函數中,咱們能夠看到 layer 在從新繪製時,若是這個 layer 不爲空,會先調用removeAllChildren 清空這個layer 的全部的子節點
void removeAllChildren() {
Layer child = firstChild;
while (child != null) {
final Layer next = child.nextSibling;
child._previousSibling = null;
child._nextSibling = null;
dropChild(child);
child = next;
}
_firstChild = null;
_lastChild = null;
}
複製代碼
這個方法將 該節點 _previousSibling,_nextSibling 設置爲空, 同時調用 dropChild
(13) dropChild 經過調用 圖層節點的 detach 方法 重置節點的owner值
void dropChild(covariant AbstractNode child) {
child._parent = null;
if (attached)
child.detach();
}
複製代碼
dropChild 函數中又調用了 dettach
(14) dettach 將 owner 值設置爲空
@mustCallSuper
void detach() {
_owner = null;
}
複製代碼
最初始的 owner 是什麼呢?
(15) RenderView -> scheduleInitialFrame
void scheduleInitialFrame() {
scheduleInitialLayout();
scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
}
複製代碼
(16) _updateMatricesAndCreateNewRootLayer
Layer _updateMatricesAndCreateNewRootLayer() {
final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform);
rootLayer.attach(this);
return rootLayer;
}
複製代碼
此時 owner 指向 this, 也就是 renderView
(17)普通繪製,以 _SelectToggleButtonRenderObject 爲例
void paint(PaintingContext context, Offset offset) {
super.paint(context, offset);
switch (textDirection) {
case TextDirection.ltr:
final Path leadingPath = Path()
..moveTo(outer.right, rrect.bottom)
..lineTo(rrect.left + rrect.blRadiusX, rrect.bottom)
..addArc(blCorner, math.pi / 2.0, sweepAngle)
..lineTo(rrect.left, rrect.top + rrect.tlRadiusY)
..addArc(tlCorner, math.pi, sweepAngle)
..lineTo(outer.right, rrect.top);
context.canvas.drawPath(leadingPath, leadingPaint);
case TextDirection.rtl:
break;
}
}
複製代碼
使用PaintingContext的畫布canvas來繪製路徑等等,這裏的繪製都是在一個 PictureLayer 的圖層上所作的, 在初始化paintContext時,會生成一個默認的圖層 PictureLayer
@override
Canvas get canvas {
if (_canvas == null)
_startRecording();
return _canvas;
}
void _startRecording() {
_currentLayer = PictureLayer(estimatedBounds);
_recorder = ui.PictureRecorder();
_canvas = Canvas(_recorder);
_containerLayer.append(_currentLayer);
複製代碼