相對於React Native
、Weex
等跨平臺框架,Flutter
擁有本身的UI繪製體系,避免了React Native
、Weex
等跨平臺框架與Native
系統的橋接,從而更好的提高了性能。html
在Flutter
中,UI都是一幀一幀的繪製,但這繪製的背後都會通過以下階段。node
Widget
的大小及位置的肯定。Widget
大小及位置來繪製UI。Element
樹中移除無用的Element
對象及處理繪製結束回調。下面就來分析上述的各個階段git
該階段主要是處理動畫及微任務。先來看動畫的處理,在使用動畫時,不少時候都會添加一個回調函數來進行狀態獲取或數據更新,如經過addListener
、addStatusListener
等函數來添加,而這些回調函數就會在本階段來執行。具體是在SchedulerBinding
中的handleBeginFrame
函數中實現。github
void handleBeginFrame(Duration rawTimeStamp) {
...
try {
// TRANSIENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.transientCallbacks;
//切換爲transientCallbacks階段
final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
//清空已註冊的回調函數
_transientCallbacks = <int, _FrameCallbackEntry>{};
//遍歷全部註冊的回調方法
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
//執行已經註冊的回調函數
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
});
_removedIds.clear();
} finally {
//切換爲midFrameMicrotasks階段
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}
}
複製代碼
_invokeFrameCallback
就會調用在使用動畫時註冊的回調函數,這裏僅執行一次。若是咱們在運行時調用_invokeFrameCallback
函數的代碼註釋調,那麼就沒法獲取動畫的狀態,從而影響動畫的正確執行。緩存
當回調函數執行完畢後,就會進入微任務階段,在該階段會執行一系列微任務,因爲這涉及到Flutter
的異步任務體系,所以這裏就再也不敘述。數據結構
在上一階段執行完畢後,就進入build階段,在該階段主要是從新構建標記爲「髒」的Widget
節點及將須要更新的RenderObject
對象標記爲「髒」。app
當handleBeginFrame
函數執行完畢後,就會執行handleDrawFrame
函數,該函數在SchedulerBinding
對象初始化時會與Window
相關聯,因此除第一次須要主動調用外,其餘時候皆是經過Window
來調用該函數。框架
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
Timeline.finishSync(); // end the "Animate" phase
try {
//持久幀回調,該回調會一直存在,不會移除
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
//當前幀繪製完成回調
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
//當執行這裏時,表明當前幀已經繪製完畢
for (FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
} finally {
//進入空閒狀態
_schedulerPhase = SchedulerPhase.idle;
Timeline.finishSync(); // end the Frame
profile(() {
_profileFrameStopwatch.stop();
_profileFramePostEvent();
});
_currentFrameTimeStamp = null;
}
}
複製代碼
這裏重點關注持久幀回調,該回調也是UI繪製的關鍵函數,是在RendererBinding
對象初始化時註冊的。異步
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
...
//註冊持久幀回調
addPersistentFrameCallback(_handlePersistentFrameCallback);
}
void _handlePersistentFrameCallback(Duration timeStamp) {
//繪製幀
drawFrame();
}
//繪製幀
void drawFrame() {
//對Widget進行測量、佈局
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
//對Widget進行繪製
pipelineOwner.flushPaint();
//發送數據給GPU
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
}
複製代碼
根據函數名能夠發現並無發現關於構建Widget
的相關函數,那麼在什麼時候構建尼?經過查看源碼能夠發現,在WidgetsBinding
中重寫了drawFrame
函數。在該函數中會建立新的Widget
對象替換舊的Widget
對象並將不須要的Element
節點從樹中移除。ide
@override
void drawFrame() {
...
try {
//若是根結點存在,就從新構建Widget
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
//調用RendererBinding中的drawFrame函數
super.drawFrame();
//移除再也不使用的Element節點
buildOwner.finalizeTree();
} finally {...}
...
}
複製代碼
Widget
對象的建立是在buildScope()
函數中實現的,這是一個很是重要的函數,具體實現以下。
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
try {
//「髒」節點列表須要從新排序
_scheduledFlushDirtyElements = true;
...
//將標記爲「髒」的Element節點根據深度進行排序
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
//標記爲「髒」的Element節點數量
int dirtyCount = _dirtyElements.length;
int index = 0;
//遍歷「髒」節點
while (index < dirtyCount) {
try {
//從新構建Widget,及是否複用當前Element
_dirtyElements[index].rebuild();
} catch (e, stack) {
...
}
index += 1;
//當_dirtyElements集合中的「髒」節點還未處理完畢時,又添加了新的「髒」節點
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
//根據「髒」節點的深度進行排序
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;
//若是當前節點的深度比新加入的「髒」節點深度要深,則須要將處理座標指向新加入的「髒」節點
while (index > 0 && _dirtyElements[index - 1].dirty) {
index -= 1;
}
}
}
} finally {
//清除_dirtyElements中全部節點的「髒」狀態
for (Element element in _dirtyElements) {
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
Timeline.finishSync();
}
}
複製代碼
_dirtyElements
是一個集合,存儲了全部標記爲「髒」的節點。在對其中的「髒」節點進行處理時,須要首先對集合中的「髒」節點進行排序,其排序規則以下。
在排序完成後,就要遍歷該集合,對其中的「髒」節點進行處理。在這裏調用的是rebuild
函數,經過該函數,會從新建立「髒」節點下的全部Widget
對象,並根據新的Widget
對象來判斷是否須要重用Element
對象。通常只要不是增刪Widget
,Element
對象都會被重用,從而也就會重用RenderObject
對象。因爲Widget
是一個很是輕量級的數據結構,因此在UI更新時作到了把性能損耗降到最低。
這裏要注意一點的是,若是_dirtyElements
中的「髒」節點還未處理完畢,就又新增了「髒」節點,那麼這時候就會從新排序,保證_dirtyElements
集合的左側永遠是「乾淨」節點,右側永遠是「髒」節點。
因爲rebuild
函數比較重要,這裏就重點介紹一下該函數,在rebuild
函數中會調用performRebuild
函數,該函數是一個抽象函數,在其子類實現,而標記爲「髒」的Element
都是StatefulElement
。因此就來StatefulElement
或者其父類中查找performRebuild
函數。
abstract class ComponentElement extends Element {
...
@override
void performRebuild() {
Widget built;
try {
//從新建立新的`Widget`對象
built = build();
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
//當構建Widget對象出錯時展現的默認頁面,能夠修改該頁面來使異常界面更友好的顯示
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
} finally {
//清除「髒」標記
_dirty = false;
}
try {
//更新子Element對應的Widget對象
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
//當構建Widget對象出錯時展現的默認頁面
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
_child = updateChild(null, built, slot);
}
}
}
複製代碼
performRebuild
函數作的事很簡單,就是建立新的Widget
對象來替換舊的對象。上面的build
函數調用的就是State
類中的build
函數,而後再調用Element
的updateChild
函數,該函數在Flutter之Widget層級介紹中進行了簡單的介紹,就是更新Element
對應的Widget
對象。而在updateChild
函數中又會調用子Element
的update
函數,從而調用子Element
的performRebuild
,而後在調用子Element
的updateChild
、update
函數。以此類推,從而更新其全部子Element
的Widget
對象。
updateRenderObject
函數來更新
RenderObject
。在更新
RenderObject
對象時,會根據狀況來對須要從新佈局及從新繪製的
RenderObject
對象進行標記。而後等待下一次的Vsync信號時來從新佈局及繪製UI。
對於RenderObject
對象,能夠經過markNeedsLayout
及markNeedsPaint
來標記是否須要從新佈局及從新繪製。但在當前階段只會調用markNeedsLayout
來標記須要從新佈局的RenderObject
對象,在下一階段纔會標記須要從新繪製的RenderObject
,因此先來看markNeedsLayout
函數。
void markNeedsLayout() {
...
//判斷佈局邊界是不是是當前RenderObject對象
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
//標記當前RenderObject及其子RenderObject對象須要從新佈局
//將當前`RenderObject`添加到集合中。
owner._nodesNeedingLayout.add(this);
owner.requestVisualUpdate();
}
}
}
@protected
void markParentNeedsLayout() {
_needsLayout = true;
final RenderObject parent = this.parent;
if (!_doingThisLayoutWithCallback) {
//調用父類的markNeedsLayout函數
parent.markNeedsLayout();
} else {
assert(parent._debugDoingThisLayout);
}
assert(parent == this.parent);
}
複製代碼
markNeedsLayout
函數的代碼實現很簡單,就是不斷遍歷父RenderObject
對象,從而找到佈局邊界的RenderObject
對象,並將該RenderObject
對象添加到集合_nodesNeedingLayout
中,而後在下一階段就從該對象開始佈局。
在這裏有個「佈局邊界」的概念,在Flutter
中,能夠給任意節點設置佈局邊界,即當邊界內的任何對象發生從新佈局時,不會影響邊界外的對象,反之亦然。
在從新構建build
函數及標記RenderObject
完成後,就進入下一階段,開始佈局。
在該階段,會肯定每一個組件的大小及位置,至關於Android中的onMeasure
+onLayout
函數所實現的功能。若是是第一次調用該函數,該階段就會遍歷全部的組件,來肯定其大小及位置;不然該階段就會遍歷佈局邊界內的全部組件,來肯定其大小及位置。
當上一階段中的buildOwner.buildScope(renderViewElement)
函數執行完畢後,就會調用RendererBinding
的drawFrame
函數,該函數實現很是簡潔。
//繪製幀
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.
}
複製代碼
其中flushLayout
就是進行組件的大小及位置肯定,在該函數中會遍歷集合_nodesNeedingLayout
並調用集合中每一個對象的_layoutWithoutResize
函數。
void flushLayout() {
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)) {
//調用RenderObject對象的_layoutWithoutResize函數
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
} finally {...}
}
複製代碼
_layoutWithoutResize
函數是私有的,因此不存在重寫的問題。那麼就直接來看該函數。
void _layoutWithoutResize() {
try {
performLayout();
markNeedsSemanticsUpdate();
} catch (e, stack) {...}
_needsLayout = false;
markNeedsPaint();
}
複製代碼
_layoutWithoutResize
函數很簡單,就直接調用了performLayout
函數。
因爲performLayout
是一個抽象函數,須要在子類重寫,但都會在該函數中調用layout
函數,而後又在layout
函數中調用performLayout
函數。以此類推,從而肯定更新UI部分的組件大小及位置,整體流程以下。
RenderObject
對象的size也不是隨便肯定的,由於在調用
RenderObject
的
layout
函數時,會傳遞一個繼承自
Constraints
的對象。該對象是一個佈局約束,由父傳給子,子會根據該對象來決定本身的大小。
當大小及位置肯定後,就又會對RenderObject
進行一次標記,此次跟上一階段的標記大同小異,但此次是標記可繪製的RenderObject
對象,而後在後面對這些對象進行從新繪製。標記可繪製的RenderObject
對象是經過markNeedsPaint
函數來實現的,代碼以下。
void markNeedsPaint() {
if (_needsPaint)
return;
_needsPaint = true;
if (isRepaintBoundary) {
//標記須要從新繪製的RenderObject對象
//須要繪製當前圖層
if (owner != null) {
owner._nodesNeedingPaint.add(this);
owner.requestVisualUpdate();
}
} else if (parent is RenderObject) {
//沒有本身的圖層,與父類共用同一圖層
final RenderObject parent = this.parent;
//遍歷其父RenderObject對象
parent.markNeedsPaint();
} else {
//當是RenderView時,須要本身建立新的圖層
if (owner != null)
owner.requestVisualUpdate();
}
}
複製代碼
markNeedsPaint
函數中涉及到了一個「重繪邊界」的概念。在進入和走出重繪邊界時,Flutter會強制切換新的圖層,這樣就能夠避免邊界內外的互相影響。固然重繪邊界也能夠在任何節點手動設置,可是通常不須要咱們來實現,Flutter提供的控件默認會在須要設置的地方自動設置。
在組件的大小及位置肯定後,就會進入當前階段。該階段主要是作一件事,就是將RenderObject
樹上新增及刪除的RenderObject
對象標記爲「髒」,方便在下一階段對這些RenderObject
對象進行重繪。具體代碼實現是在flushCompositingBits
函數中,該函數在Layout
階段後當即調用。
void flushCompositingBits() {
...
//將RenderObject對象按照深度進行排序
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
//將RenderObject對象及其子對象標記爲「髒」
node._updateCompositingBits();
}
_nodesNeedingCompositingBitsUpdate.clear();
...
}
複製代碼
_nodesNeedingCompositingBitsUpdate
是一個集合,只有RenderObject
對象的_needsCompositing
爲true時,纔會添加到該集合中。在RenderObject
對象建立時,_needsCompositing
的值會根據isRepaintBoundary
及alwaysNeedsCompositing
來共同判斷。
RenderObject() {
//isRepaintBoundary決定當前RenderObject是否與父RenderObject分開繪製,默認爲false,其值在當前對象的生命週期內沒法修改。也就是判斷當前對象是不是繪製邊界
//alwaysNeedsCompositing爲true表示當前RenderObject會一直重繪,如視頻播放,默認爲false
_needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
}
複製代碼
而後在向樹中添加或者刪除RenderObject
對象時會調用adoptChild
及dropChild
函數,而這兩個函數都會調用markNeedsCompositingBitsUpdate
函數,也就在markNeedsCompositingBitsUpdate
函數內完成了將當前對象添加到集合中的操做。
//向樹中添加當前節點
@override
void adoptChild(RenderObject child) {
setupParentData(child);
markNeedsLayout();
//將當前對象的_needsCompositingBitsUpdate值標爲true
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
super.adoptChild(child);
}
//從樹中移除當前節點
@override
void dropChild(RenderObject child) {
child._cleanRelayoutBoundary();
child.parentData.detach();
child.parentData = null;
super.dropChild(child);
markNeedsLayout();
//將當前對象的_needsCompositingBitsUpdate值標爲true
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
}
//
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;
}
}
//將當前對象或者其父對象添加到_nodesNeedingCompositingBitsUpdate集合中
if (owner != null)
owner._nodesNeedingCompositingBitsUpdate.add(this);
}
複製代碼
這樣就會在調用flushCompositingBits
函數時,就會調用_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;
}
複製代碼
通過前面的佈局及「髒」RenderObject
對象的標記,如今就能夠在圖層上進行UI的繪製。經過調用flushPaint
函數就能夠重繪已經標記的「髒」RenderObject
對象及其子對象。
void flushPaint() {
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) {
//當前對象是否與layer進行關聯
if (node._layer.attached) {
//在圖層上繪製UI
PaintingContext.repaintCompositedChild(node);
} else {
//跳過UI繪製,但當前節點爲「髒」的狀態不會改變
node._skippedPaintingOnLayer();
}
}
}
} finally {}
}
複製代碼
flushPaint
函數中,每次遍歷「髒」RenderObject
對象時,都會進行一次排序,避免重複繪製。而後在判斷當前對象是否與Layer
進行關聯,若是沒有關聯,則沒法進行繪製,但不會清除「髒」標記。下面來看repaintCompositedChild
函數的實現。
static void repaintCompositedChild(RenderObject child, { bool
_repaintCompositedChild(
child,
debugAlsoPaintedParent: debugAlsoPaintedParent,
);
}
static void _repaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
PaintingContext childContext,
}) {
//拿到Layer對象
OffsetLayer childLayer = child._layer;
if (childLayer == null) {
//建立新的Layer對象
child._layer = childLayer = OffsetLayer();
} else {
//移除Layer對象的後繼節點
childLayer.removeAllChildren();
}
//建立context對象
childContext ??= PaintingContext(child._layer, child.paintBounds);
//調用paint函數開始繪製
child._paintWithContext(childContext, Offset.zero);
childContext.stopRecordingIfNeeded();
}
複製代碼
在該函數中主要是對Layer
對象的處理,而後調用_paintWithContext
函數,在_paintWithContext
函數中就會調用paint
這個函數,從而實現UI的繪製。至此,就完成了UI的繪製,下面再來看一個被忽略的對象——Layer
。
Layer
是「圖層」意思,在Flutter
中是最容易被忽略但又無比重要的一個類。它很是貼近底層,能夠很容易的看到調用Native方法。
Layer
跟其餘三棵樹同樣,也是一棵樹,有「髒」狀態的標記、更新等操做。不一樣的是,Layer
是一個雙鏈表結構,在每一個Layer
對象中都會指向其前置節點與後置節點(葉子Layer
的後置節點爲null)。
abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
//返回父節點
@override
ContainerLayer get parent => super.parent;
//當前節點狀態,爲true表示當前節點是「髒」數據。須要重繪
bool _needsAddToScene = true;
//將當前節點標記爲「髒」
@protected
@visibleForTesting
void markNeedsAddToScene() {
// Already marked. Short-circuit.
if (_needsAddToScene) {
return;
}
_needsAddToScene = true;
}
@protected
bool get alwaysNeedsAddToScene => false;
//這個是一個很是重要的東西,主要用於節點數據的緩存。存儲當前節點的渲染數據,若是當前節點不須要更新,就直接拿存儲的數據使用。
@protected
ui.EngineLayer get engineLayer => _engineLayer;
//更改當前節點的數據
@protected
set engineLayer(ui.EngineLayer value) {
_engineLayer = value;
if (parent != null && !parent.alwaysNeedsAddToScene) {
//將父節點標記須要更新的狀態
parent.markNeedsAddToScene();
}
}
}
ui.EngineLayer _engineLayer;
//更新當前節點狀態,若是_needsAddToScene爲true,則將當前節點標記爲「髒」
@protected
@visibleForTesting
void updateSubtreeNeedsAddToScene() {
_needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
}
//指向後置節點
Layer get nextSibling => _nextSibling;
Layer _nextSibling;
//指向前置節點
Layer get previousSibling => _previousSibling;
Layer _previousSibling;
//將子節點從Layer樹中移除
@override
void dropChild(AbstractNode child) {
if (!alwaysNeedsAddToScene) {
markNeedsAddToScene();
}
super.dropChild(child);
}
//將當前節點添加到Layer樹中
@override
void adoptChild(AbstractNode child) {
if (!alwaysNeedsAddToScene) {
markNeedsAddToScene();
}
super.adoptChild(child);
}
//將當前節點從Layer樹中移除
@mustCallSuper
void remove() {
parent?._removeChild(this);
}
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]);
void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {
//使用當前節點的緩存的數據
if (!_needsAddToScene && _engineLayer != null) {
builder.addRetained(_engineLayer);
return;
}
addToScene(builder);
//將當前節點標記爲「乾淨」的
_needsAddToScene = false;
}
}
複製代碼
previousSibling
與nextSibling
分別是Layer
的前置節點與後置節點,當向Layer
樹中添加Layer
節點時,也會將當前Layer
設置爲父節點的後置節點,父節點設置爲當前節點的前置節點。這樣,就造成了一顆樹。
class ContainerLayer extends Layer {
...
//將當前節點及其鏈表上的全部子節點都加入到Layer樹中
@override
void attach(Object owner) {
super.attach(owner);
Layer child = firstChild;
while (child != null) {
child.attach(owner);
child = child.nextSibling;
}
}
//將當前節點及其鏈表上的全部子節點都從Layer樹中移除
@override
void detach() {
super.detach();
Layer child = firstChild;
while (child != null) {
child.detach();
child = child.nextSibling;
}
}
//將child添加到鏈表中
void append(Layer child) {
adoptChild(child);
child._previousSibling = lastChild;
if (lastChild != null)
lastChild._nextSibling = child;
_lastChild = child;
_firstChild ??= child;
}
...
}
複製代碼
在上述的append
函數中就將子節點添加到Layer
樹並加入到雙鏈表中。在adoptChild
函數中最終會調用attach
函數,從而完成Layer
樹的添加。
_needsAddToScene
是對Layer
狀態的標記,若是爲true,則表示當前Layer
須要重寫進行繪製,不然表示當前Layer
是「乾淨」的,不須要從新繪製,只須要拿Layer
上次的數據與其餘Layer
數據一塊兒交給GPU處理便可。從而達到節省資源的目的。
class ContainerLayer extends Layer {
...
//更新Layer節點的狀態。
@override
void updateSubtreeNeedsAddToScene() {
super.updateSubtreeNeedsAddToScene();
Layer child = firstChild;
while (child != null) {
child.updateSubtreeNeedsAddToScene();
_needsAddToScene = _needsAddToScene || child._needsAddToScene;
child = child.nextSibling;
}
}
...
}
複製代碼
updateSubtreeNeedsAddToScene
函數就是更新Layer
的狀態,能夠發現,在更新當前Layer
的狀態時,也會更新其全部子Layer
的狀態。
關於Layer
的更多內容能夠去閱讀Flutter Framework 源碼解析( 2 )—— 圖層詳解這篇文章。
該階段主要是將更新後的數據傳遞給GPU。這時候調用的是compositeFrame
函數,該函數很簡單,就是調用了一個Native函數。
void compositeFrame() {
Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
//更新後數據交給GPU處理
_window.render(scene);
scene.dispose();
} finally {
Timeline.finishSync();
}
}
複製代碼
在向GPU發送數據後,Flutter
還會調用flushSemantics
函數。該函數與系統的輔助功能相關,通常狀況下是不作任何處理。
在該階段,主要是將Element
對象從樹中移除及處理添加在_postFrameCallbacks
集合中的回調函數。因爲該回調函數是在繪製結束時調用,因此在該回調函數中,context已經建立成功。
能夠發現,Flutter
的UI繪製仍是蠻複雜的,涉及到的東西也比較多,如動畫的處理、輔助功能、異步任務等。但總體上仍是經過Widget
、Element
、RenderObject
這三棵樹來操做layer
樹實現的UI的繪製。 熟悉了這四棵樹,也就會對Flutter
的UI繪製有一個清晰的認識。