《Flutter框架分析(一)-- 總覽和Window》node
《Flutter框架分析(二)-- 初始化》canvas
《Flutter框架分析(三)-- Widget,Element和RenderObject》性能優化
《Flutter框架分析(四)-- Flutter框架的運行》app
本篇文章會結合Flutter源碼給你們介紹一下渲染流水線最後一步的繪製(paint)階段。本文涉及的內容可能離你們平時開發Flutter app所須要知道的框架知識相對於前面幾章會跟遙遠一些。目前可能須要注意的地方就是RepaintBoundary
這個Widget
,其對應的RenderObject
是RenderRepaintBoundary
。這個Widget
的做用在介紹完渲染流水線的繪製階段相信你們會有一個更明確的理解。源碼分析
咱們都知道,Flutter框架中render tree負責佈局和渲染。在渲染的時候,Flutter會遍歷須要重繪的RenderObject
子樹來逐一繪製。咱們在屏幕上看到的Flutter app頁面實際上是由不一樣的圖層(layers)組合(compsite)而成的。這些圖層是以樹的形式組織起來的,也就是咱們在Flutter中見到的又一個比較重要的樹:layer tree。佈局
paint()
函數這麼簡單了,而是不少地方都涉及到layer tree的管理。
Flutter中的圖層用類Layer
來表明。post
abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
@override
ContainerLayer get parent => super.parent;
Layer get nextSibling => _nextSibling;
Layer _nextSibling;
Layer get previousSibling => _previousSibling;
Layer _previousSibling;
}
複製代碼
類Layer
是個抽象類,和RenderObject
同樣,繼承自AbstractNode
。代表它也是個樹形結構。屬性parent
表明其父節點,類型是ContainerLayer
。這個類繼承自Layer
。只有ContainerLayer
類型及其子類的圖層能夠擁有孩子,其餘類型的Layer
子類都是葉子圖層。nextSibling
和previousSibling
表示同一圖層的前一個和後一個兄弟節點,也就是圖層孩子節點們是用雙向鏈表存儲的。
class ContainerLayer extends Layer {
Layer _firstChild;
Layer _lastChild;
void append(Layer child) {
adoptChild(child);
child._previousSibling = lastChild;
if (lastChild != null)
lastChild._nextSibling = child;
_lastChild = child;
_firstChild ??= child;
}
void _removeChild(Layer child) {
if (child._previousSibling == null) {
_firstChild = child._nextSibling;
} else {
child._previousSibling._nextSibling = child.nextSibling;
}
if (child._nextSibling == null) {
_lastChild = child.previousSibling;
} else {
child.nextSibling._previousSibling = child.previousSibling;
}
child._previousSibling = null;
child._nextSibling = null;
dropChild(child);
}
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;
}
}
複製代碼
ContainerLayer
增長了頭和尾兩個孩子節點屬性,並提供了新增及刪除孩子節點的方法。
ContainerLayer
的子類有OffsetLayer
,ClipRectLayer
等等。
葉子類型的圖層有TextureLayer
,PlatformViewLayer
, PerformanceOverlayLayer
,PictureLayer
等等,框架中大部分RenderObject
的繪製的目標圖層都是PictureLayer
。
class PictureLayer extends Layer {
final Rect canvasBounds;
ui.Picture _picture;
}
複製代碼
屬性canvasBounds
表明圖層畫布的邊界,但這個屬性是建議性質的。 屬性picture
來自dart:ui
庫。
回到咱們熟悉的drawFrame()
函數中,pipelineOwner.flushLayout()
調用完成之後渲染流水線就進入了繪製(paint)階段。
void drawFrame() {
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
複製代碼
繪製階段的第一個調用是pipelineOwner.flushCompositingBits()
。
這個調用是用來更新render tree 中RenderObject
的_needsCompositing
標誌位的。
在介紹這個調用以前咱們,咱們先來了解一些RenderObject
的標誌位。
bool _needsCompositing
:標誌自身或者某個孩子節點有合成層(compositing layer)。若是當前節點須要合成,那麼全部祖先節點也都須要合成。
bool _needsCompositingBitsUpdate
:標誌當前節點是否須要更新_needsCompositing
。這個標誌位由下方的markNeedsCompositingBitsUpdate()
函數設置。
bool get isRepaintBoundary => false;
:標誌當前節點是否與父節點分開來重繪。當這個標誌位爲true
的時候,父節點重繪的時候子節點不必定也須要重繪,一樣的,當自身重繪的時候父節點不必定須要重繪。此標誌位爲true
的RenderObject
有render tree的根節點RenderView
,有咱們熟悉的RenderRepaintBoundary
,TextureBox
等。
bool get alwaysNeedsCompositing => false;
:標誌當前節點是否老是須要合成。這個標誌位爲true
的話意味着當前節點繪製的時候老是會新開合成層(composited layer)。例如TextureBox
, 以及咱們熟悉的顯示運行時性能的RenderPerformanceOverlay
等。
在渲染流水線的構建階段,有些狀況下render tree裏的節點須要從新更新_needsCompositing
,好比說render tree裏節點的增長,刪除。這個標記工做由函數markNeedsCompositingBitsUpdate()
完成。
void markNeedsCompositingBitsUpdate() {
if (_needsCompositingBitsUpdate)
return;
_needsCompositingBitsUpdate = true;
if (parent is RenderObject) {
final RenderObject parent = this.parent;
if (parent._needsCompositingBitsUpdate)
return;
if (!isRepaintBoundary && !parent.isRepaintBoundary) {
parent.markNeedsCompositingBitsUpdate();
return;
}
}
if (owner != null)
owner._nodesNeedingCompositingBitsUpdate.add(this);
}
複製代碼
這個調用會從當前節點往上找,把全部父節點的_needsCompositingBitsUpdate
標誌位都置位true
。直到本身或者父節點的isRepaintBoundary
爲true
。最後會把本身加入到PipelineOwner
的_nodesNeedingCompositingBitsUpdate
列表裏面。而函數調用pipelineOwner.flushCompositingBits()
正是用來處理這個列表的。
flushCompositingBits()
源碼以下:
void flushCompositingBits() {
_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();
}
複製代碼
首先把列表_nodesNeedingCompositingBitsUpdate
按照節點在樹中的深度排序。而後遍歷調用node._updateCompositingBits()
void _updateCompositingBits() {
if (!_needsCompositingBitsUpdate)
return;
final bool oldNeedsCompositing = _needsCompositing;
_needsCompositing = false;
visitChildren((RenderObject child) {
child._updateCompositingBits();
if (child.needsCompositing)
_needsCompositing = true;
});
if (isRepaintBoundary || alwaysNeedsCompositing)
_needsCompositing = true;
if (oldNeedsCompositing != _needsCompositing)
markNeedsPaint();
_needsCompositingBitsUpdate = false;
}
複製代碼
這裏作的事情是從當前節點往下找,若是某個子節點isRepaintBoundary
爲true
或alwaysNeedsCompositing
爲true
則設置_needsCompositing
爲true
。子節點這個標誌位爲true
的話,那麼父節點的該標誌位也會被設置爲true
。若是_needsCompositing
發生了變化,那麼會調用markNeedsPaint()
通知渲染流水線本RenderObject
須要重繪了。爲啥要重繪呢?緣由是本``RenderObject`所在的圖層(layer)可能發生了變化。
函數flushPaint()
處理的是以前加入到列表_nodesNeedingPaint
裏的節點。當某個RenderObject
須要被重繪的時候會調用markNeedsPaint()
void markNeedsPaint() {
if (_needsPaint)
return;
_needsPaint = true;
if (isRepaintBoundary) {
if (owner != null) {
owner._nodesNeedingPaint.add(this);
owner.requestVisualUpdate();
}
} else if (parent is RenderObject) {
final RenderObject parent = this.parent;
parent.markNeedsPaint();
} else {
if (owner != null)
owner.requestVisualUpdate();
}
}
複製代碼
函數markNeedsPaint()
首先作的是把本身的標誌位_needsPaint
設置爲true
。而後會向上查找最近的一個isRepaintBoundary
爲true
的祖先節點。直到找到這樣的節點,纔會把這個節點加入到_nodesNeedingPaint
列表中,也就是說,並非任意一個須要重繪的RenderObject
就會被加入這個列表,而是往上找直到找到最近的一個isRepaintBoundary
爲true
纔會放入這個列表,換句話說,這個列表裏只有isRepaintBoundary
爲true
這種類型的節點。也就是說重繪的起點是從「重繪邊界」開始的。
void flushPaint() {
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
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 {
...
}
}
複製代碼
在處理須要重繪的節點的時候,會先給這些節點作個排序,這裏須要注意的是,和以前flushLayout()
裏的排序不一樣,這裏的排序是深度度深的節點在前。在循環體裏,會判斷當前節點的_layer
屬性是否處於attached
的狀態。若是_layer.attached
爲true
的話調用PaintingContext.repaintCompositedChild(node);
去作繪製,不然的話調用node._skippedPaintingOnLayer()
將自身以及到上層繪製邊界之間的節點的_needsPaint
所有置爲true
。這樣在下次_layer.attached
變爲true
的時候會直接繪製。
從上述代碼也能夠看出,重繪邊界至關於把Flutter的繪製作了分塊處理,重繪的從上層重繪邊界開始,到下層重繪邊界爲止,在此之間的RenderObject
都須要重繪,而邊界以外的就可能不須要重繪,這也是一個性能上的考慮,儘可能避免沒必要要的繪製。因此如何合理安排RepaintBoundary
是咱們在作Flutter app的性能優化時候須要考慮的一個方向。
這裏的_layer
屬性就是咱們以前說的圖層,這個屬性只有繪製邊界的RenderObject
纔會有值。通常的RenderObject
這個屬性是null
。
static void _repaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
PaintingContext childContext,
}) {
if (child._layer == null) {
child._layer = OffsetLayer();
} else {
child._layer.removeAllChildren();
}
childContext ??= PaintingContext(child._layer, child.paintBounds);
child._paintWithContext(childContext, Offset.zero);
childContext.stopRecordingIfNeeded();
}
複製代碼
函數_repaintCompositedChild()
會先檢查RenderObject
的圖層屬性,爲空則新建一個OffsetLayer
實例。若是圖層已經存在的話就把孩子清空。
若是沒有PaintingContext
的話會新建一個,而後讓開始繪製。咱們先來看一下PaintingContext
這個類:
class PaintingContext extends ClipContext {
@protected
PaintingContext(this._containerLayer, this.estimatedBounds)
final ContainerLayer _containerLayer;
final Rect estimatedBounds;
PictureLayer _currentLayer;
ui.PictureRecorder _recorder;
Canvas _canvas;
@override
Canvas get canvas {
if (_canvas == null)
_startRecording();
return _canvas;
}
void _startRecording() {
_currentLayer = PictureLayer(estimatedBounds);
_recorder = ui.PictureRecorder();
_canvas = Canvas(_recorder);
_containerLayer.append(_currentLayer);
}
void stopRecordingIfNeeded() {
if (!_isRecording)
return;
_currentLayer.picture = _recorder.endRecording();
_currentLayer = null;
_recorder = null;
_canvas = null;
}
複製代碼
類PaintingContext
字面意思是繪製上下文,其屬性_containerLayer
是容器圖層,來自構造時的入參。也就是說PaintingContext
是和容器圖層關聯的。接下來還有PictureLayer
類型的_currentLayer
屬性, ui.PictureRecorder
類型的_recorder
屬性和咱們熟悉的Canvas
類型的屬性_canvas
。函數_startRecording()
實例化了這幾個屬性。_recorder
用來錄製繪製命令,_canvas
綁定一個錄製器。最後,_currentLayer
會做爲子節點加入到_containerLayer
中。有開始那麼就會有結束,stopRecordingIfNeeded()
用來結束當前繪製的錄製。結束時會把繪製完畢的Picture
賦值給當前的PictureLayer.picture
。
有了PaintingContext
之後,就能夠調用RenderObject._paintWithContext()
開始繪製了,這個函數會直接調用到咱們熟悉的RenderObject.paint(context, offset)
,咱們知道函數paint()
由RenderObject
子類本身實現。從以前的源碼分析咱們知道繪製起點都是「繪製邊界」。這裏咱們就拿咱們熟悉的一個「繪製邊界」,RenderRepaintBoundary
,爲例來走一下繪製流程,它的繪製函數的實如今RenderProxyBoxMixin
類中:
@override
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChild(child, offset);
}
複製代碼
這個調用又回到了PaintingContext
的paintChild()
方法:
void paintChild(RenderObject child, Offset offset) {
if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
_compositeChild(child, offset);
} else {
child._paintWithContext(this, offset);
}
}
複製代碼
這裏會檢查子節點是否是繪製邊界,若是不是的話,就是普通的繪製了,接着往下調用_paintWithContext()
,繼續往當前的PictureLayer
上繪製。若是是的話就把當前的繪製先停掉。而後調用_compositeChild(child, offset);
void _compositeChild(RenderObject child, Offset offset) {
if (child._needsPaint) {
repaintCompositedChild(child, debugAlsoPaintedParent: true);
}
child._layer.offset = offset;
appendLayer(child._layer);
}
複製代碼
若是這個子繪製邊界被標記爲須要重繪的話,那麼就調用repaintCompositedChild()
來從新生成圖層而後重繪。若是這個子繪製邊界沒有被標記爲須要重繪的話,就跳過了從新生成圖層和重繪。最後只須要把子圖層加入到當前容器圖層中就好了。
上面說的是子節點是繪製邊界的時候的繪製流程,那若是子節點是普通的一個RenderObject
呢?這裏就拿Flutter app出錯控件的繪製作個例子:
void paint(PaintingContext context, Offset offset) {
try {
context.canvas.drawRect(offset & size, Paint() .. color = backgroundColor);
double width;
if (_paragraph != null) {
// See the comment in the RenderErrorBox constructor. This is not the
// code you want to be copying and pasting. :-)
if (parent is RenderBox) {
final RenderBox parentBox = parent;
width = parentBox.size.width;
} else {
width = size.width;
}
_paragraph.layout(ui.ParagraphConstraints(width: width));
context.canvas.drawParagraph(_paragraph, offset);
}
} catch (e) {
// Intentionally left empty.
}
}
複製代碼
這看起來就像個正常的繪製了,咱們會用來自PaintingContext
的畫布canvas
來繪製矩形,繪製文本等等。從前面的分析也能夠看出,這裏的繪製都是在一個PictureLayer
的圖層上所作的。
至此 pipelineOwner.flushPaint();
這個函數的調用就跑完了,經過分析咱們能夠知道,繪製工做其實主要是在這個函數中完成的。接下來咱們再來看一下繪製流程的最後一個重要的函數調用:
這裏的renderView
就是咱們以前說的render tree的根節點。這個函數調用主要是把整個layer tree生成scene
送到engine去顯示。
void compositeFrame() {
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
_window.render(scene);
scene.dispose();
} finally {
Timeline.finishSync();
}
}
複製代碼
ui.SceneBuilder()
最終調用Native方法SceneBuilder_constructor
。也就是說ui.SceneBuilder
實例是由engine建立的。接下來就是調用layer.buildScene(builder)
方法,這個方法會返回一個ui.Scene
實例。因爲方法compositeFrame()
的調用者是renderView
。因此這裏這個layer
是來自renderView
的屬性,咱們前面說過只有繪製邊界節點纔有layer
。因此可見render tree的根節點renderView
也是一個繪製邊界。那麼這個layer
是從哪裏來的呢?在文章《Flutter框架分析(二)-- 初始化》咱們講過,框架初始化的過程當中renderView
會調度開天闢地的第一幀:
void scheduleInitialFrame() {
scheduleInitialLayout();
scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
owner.requestVisualUpdate();
}
Layer _updateMatricesAndCreateNewRootLayer() {
_rootTransform = configuration.toMatrix();
final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform);
rootLayer.attach(this);
return rootLayer;
}
void scheduleInitialPaint(ContainerLayer rootLayer) {
_layer = rootLayer;
owner._nodesNeedingPaint.add(this);
}
複製代碼
在方法_updateMatricesAndCreateNewRootLayer()
中,咱們看到這裏實例化了一個TransformLayer
。TransformLayer
繼承自OffsetLayer
。構造時須要傳入Matrix4
類型的參數transform
。這個Matrix4
其實和咱們在Android中見到的Matrix
是一回事。表明着矩陣變換。這裏的transform
來自咱們以前講過的ViewConfiguration
,它就是把設備像素比例轉化成了矩陣的形式。最終這個layer
關聯上了renderView
。因此這裏這個TransformLayer
其實也是layer tree的根節點了。
回到咱們的繪製流程。layer.buildScene(builder);
這個調用咱們天然是去 TransformLayer
裏找了,但這個方法是在其父類OffsetLayer
內,從這個調用開始就都是對圖層進行操做,最終把layer tree轉換爲場景scene
:
ui.Scene buildScene(ui.SceneBuilder builder) {
List<PictureLayer> temporaryLayers;
updateSubtreeNeedsAddToScene();
addToScene(builder);
final ui.Scene scene = builder.build();
return scene;
}
複製代碼
函數調用updateSubtreeNeedsAddToScene();
會遍歷layer tree來設置_subtreeNeedsAddToScene
標誌位,若是有任意子圖層的添加、刪除操做,則該子圖層及其祖先圖層都會被置上_subtreeNeedsAddToScene
標誌位。而後會調用addToScene(builder);
@override
@override
ui.EngineLayer addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
_lastEffectiveTransform = transform;
final Offset totalOffset = offset + layerOffset;
if (totalOffset != Offset.zero) {
_lastEffectiveTransform = Matrix4.translationValues(totalOffset.dx, totalOffset.dy, 0.0)
..multiply(_lastEffectiveTransform);
}
builder.pushTransform(_lastEffectiveTransform.storage);
addChildrenToScene(builder);
builder.pop();
return null; // this does not return an engine layer yet.
}
複製代碼
builder.pushTransform
會調用到engine層。至關於告訴engine這裏我要加一個變換圖層。而後調用ddChildrenToScene(builder)
將子圖層加入場景中,完了還要把以前壓棧的變換圖層出棧。
void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) {
Layer child = firstChild;
while (child != null) {
if (childOffset == Offset.zero) {
child._addToSceneWithRetainedRendering(builder);
} else {
child.addToScene(builder, childOffset);
}
child = child.nextSibling;
}
}
複製代碼
這就是遍歷添加子圖層的調用。主要仍是逐層向下的調用addToScene()
。這個方法不一樣的圖層會有不一樣的實現,對於容器類圖層而言,主要就是作三件事:1.添加本身圖層的效果真後入棧,2.添加子圖層,3. 出棧。
在全部圖層都處理完成以後。回到renderView.compositeFrame()
,可見最後會把處理完獲得的場景經過_window.render(scene);
調用送入engine去顯示了。
至此渲染流水線的繪製(paint)階段就算是跑完了。
等等,好像缺了點什麼,在分析繪製的過程當中咱們看到有個主要的調用pipelineOwner.flushCompositingBits()
是在更新render tree裏節點的_needsCompositing
標誌位的。可是咱們這都把流程說完了,貌似沒有看到這個標誌位在哪裏用到啊。這個標誌位確定在哪裏被用到了,不然咱們費這麼大勁更新有啥用呢?回去再研究一下代碼......
這個標誌位某些RenderObject
在其paint()
函數中會用到,做用呢,就體如今PaintingContext
的這幾個函數的調用上了:
void pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.hardEdge }) {
final Rect offsetClipRect = clipRect.shift(offset);
if (needsCompositing) {
pushLayer(ClipRectLayer(clipRect: offsetClipRect, clipBehavior: clipBehavior), painter, offset, childPaintBounds: offsetClipRect);
} else {
clipRectAndPaint(offsetClipRect, clipBehavior, offsetClipRect, () => painter(this, offset));
}
}
void pushClipRRect(bool needsCompositing, Offset offset, Rect bounds, RRect clipRRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias }) {
final Rect offsetBounds = bounds.shift(offset);
final RRect offsetClipRRect = clipRRect.shift(offset);
if (needsCompositing) {
pushLayer(ClipRRectLayer(clipRRect: offsetClipRRect, clipBehavior: clipBehavior), painter, offset, childPaintBounds: offsetBounds);
} else {
clipRRectAndPaint(offsetClipRRect, clipBehavior, offsetBounds, () => painter(this, offset));
}
}
void pushClipPath(bool needsCompositing, Offset offset, Rect bounds, Path clipPath, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias }) {
final Rect offsetBounds = bounds.shift(offset);
final Path offsetClipPath = clipPath.shift(offset);
if (needsCompositing) {
pushLayer(ClipPathLayer(clipPath: offsetClipPath, clipBehavior: clipBehavior), painter, offset, childPaintBounds: offsetBounds);
} else {
clipPathAndPaint(offsetClipPath, clipBehavior, offsetBounds, () => painter(this, offset));
}
}
void pushTransform(bool needsCompositing, Offset offset, Matrix4 transform, PaintingContextCallback painter) {
final Matrix4 effectiveTransform = Matrix4.translationValues(offset.dx, offset.dy, 0.0)
..multiply(transform)..translate(-offset.dx, -offset.dy);
if (needsCompositing) {
pushLayer(
TransformLayer(transform: effectiveTransform),
painter,
offset,
childPaintBounds: MatrixUtils.inverseTransformRect(effectiveTransform, estimatedBounds),
);
} else {
canvas
..save()
..transform(effectiveTransform.storage);
painter(this, offset);
canvas
..restore();
}
}
複製代碼
needsCompositing
做爲這幾個函數的入參,從代碼可見其做用主要是控制這幾種特殊的繪製操做的具體實現方式,若是needsCompositing
爲true
的話,則會調用pushLayer
,參數咱們以前見過的各類圖層
void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset, { Rect childPaintBounds }) {
stopRecordingIfNeeded();
appendLayer(childLayer);
final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds);
painter(childContext, offset);
childContext.stopRecordingIfNeeded();
}
@protected
PaintingContext createChildContext(ContainerLayer childLayer, Rect bounds) {
return PaintingContext(childLayer, bounds);
}
複製代碼
流程基本上和咱們以前看到的重繪的時候新增一個圖層的操做是同樣的。
而若是needsCompositing
爲false
的話則走的是canvas
的各類變換了。你們感興趣的話能夠去看一下源碼,這裏就不細說了。
至此Flutter框架渲染流水線的繪製(paint)階段就分析完了。繪製流程並不像以前的構建,佈局流程那樣直接,只要遍歷element tree或者render tree就好了。渲染階段會出現另外一個樹,圖層樹,layer tree。整個繪製流程就是在把render tree轉化爲適合的layer tree,最後再生成場景(scene)的一個過程。
最後,在瞭解渲染過程的基礎上,推薦你們再看一下這個來自Google工程師的視頻:深刻了解 Flutter 的高性能圖形渲染。相信你們在看過這個視頻以後,會對Flutter框架的渲染,以及可能遇到的一些性能問題會有進一步的理解。