做爲系列文章的第二十一篇,本篇將經過不同的角度來介紹 Flutter Framework 的總體渲染原理,深刻剖析 Flutter 中構成 Layer 後的繪製流程,讓開發者對 Flutter 的渲染原理和實現邏輯有更清晰的認知。git
Flutter 完整實戰實戰系列文章專欄github
Flutter 番外的世界系列文章專欄canvas
先回顧下,咱們知道在 Flutter 中的控件會經歷 Widget
-> Element
-> RenderObject
-> Layer
這樣的變化過程,而其中 Layer
的組成由 RenderObject
中的 isRepaintBoundary
標誌位決定。c#
當調用
setState
時,RenderObject
就會往上的父節點去查找,根據isRepaintBoundary
是否爲 true,會決定是否從這裏開始往下去觸發重繪,換個說法就是:肯定要更新哪些區域。bash
好比 Navigator
跳轉不一樣路由頁面,每一個頁面內部就有一個 RepaintBoundary
控件,這個控件對應的 RenderRepaintBoundary
內的 isRepaintBoundary
標記位就是爲 true
,從而路由頁面之間造成了獨立的 Layer
。app
因此相關的 RenderObject
在一塊兒組成了 Layer
,而由 Layer
構成的 Layer Tree
最後會被提交到 Flutter Engine 繪製出畫面。less
那 Layer
是怎麼工做的?它的本質又是什麼? Flutter Framework 中 Layer
是如何被提交到 Engine 中?ide
帶着前面 Layer
的問題,咱們先作個假設:若是拋開 Flutter Framework 中封裝好的控件,咱們應該如何繪製出一個畫面?或者說如何建立一個 Layer
?學習
舉個例子,以下代碼所示,運行後能夠看到一個居中顯示的 100 x 100 的藍色方塊,而且代碼裏沒有用到任何 Widget
、 RenderObject
甚至 Layer
,而是使用了 PictureRecorder
、Canvas
、 SceneBuilder
這些相對陌生的對象完成了畫面繪製,而且在最後執行的是 window.render
。ui
import 'dart:ui' as ui;
void main() {
ui.window.onBeginFrame = beginFrame;
ui.window.scheduleFrame();
}
void beginFrame(Duration timeStamp) {
final double devicePixelRatio = ui.window.devicePixelRatio;
///建立一個畫板
final ui.PictureRecorder recorder = ui.PictureRecorder();
///基於畫板建立一個 Canvas
final ui.Canvas canvas = ui.Canvas(recorder);
canvas.scale(devicePixelRatio, devicePixelRatio);
var centerX = ui.window.physicalSize.width / 2.0;
var centerY = ui.window.physicalSize.height / 2.0;
///畫一個 100 的劇中藍色
canvas.drawRect(
Rect.fromCenter(
center: Offset.zero,
width: 100,
height: 100),
new Paint()..color = Colors.blue);
///結束繪製
final ui.Picture picture = recorder.endRecording();
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder()
..pushOffset(centerX, centerY)
..addPicture(ui.Offset.zero, picture)
..pop();
ui.window.render(sceneBuilder.build());
}
複製代碼
由於在 Flutter 中 Canvas
的建立是必須有 PictureRecorder
,而 PictureRecorder
顧名思義就是建立一個圖片用於記錄繪製,因此在上述代碼中:
PictureRecorder
;PictureRecorder
建立了 Canvas
;Canvas
繪製藍色小方塊;SceneBuilder
的 pushOffset
和 addPicture
加載了繪製的內容;window.render
繪製出畫面。須要注意⚠️:
render
方法被限制必須在onBeginFrame
或onDrawFrame
中調用,因此上方代碼纔會有window.onBeginFrame = beginFrame;
。在官方的examples/layers/raw/ 下有很多相似的例子。
能夠看到 Flutter Framework 在底層繪製的最後一步是 window.render
,而以下代碼所示: render
方法須要的參數是 Scene
對象,而且 render
方法是一個 native
方法,說明 Flutter Framework 最終提交給 Engine 的是一個 Scene
。
void render(Scene scene) native 'Window_render';
複製代碼
那 Scene
又是什麼?前面所說的 Layer
又在哪裏呢?它們之間又有什麼樣的關係?
在 Flutter 中 Scene
實際上是一個 Native
對象,它對應的實際上是 Engine
中的 scene.cc
結構,而 Engine 中的 scene.cc
內包含了一個 layer_tree_
用於繪製,因此首先能夠知道Scene
在 Engine
是和 layer_tree_
有關係。
而後就是在 Flutter Framework 中 Scene
只能經過 SceneBuilder
構建,而 SceneBuilder
中存在不少方法好比: pushOffset
、pushClipRect
、pushOpacity
等,這些方法的執行後,能夠經過 Engine 會建立出一個對應的 EngineLayer
。
OffsetEngineLayer pushOffset(double dx, double dy, { OffsetEngineLayer oldLayer }) {
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushOffset'));
final OffsetEngineLayer layer = OffsetEngineLayer._(_pushOffset(dx, dy));
assert(_debugPushLayer(layer));
return layer;
}
EngineLayer _pushOffset(double dx, double dy) native 'SceneBuilder_pushOffset';
複製代碼
因此 SceneBuilder
在 build
出 Scene
以前,能夠經過 push
等相關方法產生 EngineLayer
, 好比前面的藍色小方塊例子,SceneBuilder
就是經過 pushOffset
建立出對應的圖層偏移。
接着看 Flutter Framework 中的 Layer
,以下代碼所示,在 Layer
默認就存在 EngineLayer
參數,因此能夠得知 Layer
確定和 SceneBuilder
有必定關係。
@protected
ui.EngineLayer get engineLayer => _engineLayer;
@protected
set engineLayer(ui.EngineLayer value) {
_engineLayer = value;
if (!alwaysNeedsAddToScene) {
if (parent != null && !parent.alwaysNeedsAddToScene) {
parent.markNeedsAddToScene();
}
}
}
ui.EngineLayer _engineLayer;
/// Override this method to upload this layer to the engine.
///
/// Return the engine layer for retained rendering. When there no
/// corresponding engine layer, null is returned.
@protected
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]);
複製代碼
其次在 Layer
中有一個關鍵方法: addToScene
,先經過註釋能夠得知這個方法是由子類實現,而且執行後能夠獲得一個 EngineLayer
,而且這個方法須要一個 SceneBuilder
,而查詢該方法的實現剛好就有OffsetLayer
和 PictureLayer
等。
因此以下代碼所示,在 OffsetLayer
和 PictureLayer
的 addToScene
方法實現中能夠看到:
PictureLayer
調用了 SceneBuilder
的 addPicture
;OffsetLayer
調用了 SceneBuilder
的 pushOffset
;class PictureLayer extends Layer {
···
@override
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
builder.addPicture(layerOffset, picture, isComplexHint: isComplexHint, willChangeHint: willChangeHint);
}
···
}
class OffsetLayer extends ContainerLayer {
···
OffsetLayer({ Offset offset = Offset.zero }) : _offset = offset;
@override
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
engineLayer = builder.pushOffset(
layerOffset.dx + offset.dx,
layerOffset.dy + offset.dy,
oldLayer: _engineLayer as ui.OffsetEngineLayer,
);
addChildrenToScene(builder);
builder.pop();
}
···
}
複製代碼
因此到這裏 SceneBuilder
和 Layer
經過 EngineLayer
和 addToScene
方法成功關聯起來,而 window.render
提交的 Scene
又是經過 SceneBuilder
構建獲得,因此以下圖所示, Layer
和 Scene
就這樣「苟且」到了一塊兒。
對面前面的藍色小方塊代碼,以下代碼所示,這裏修改成使用 Layer
的方式實現,能夠看到這樣的實現更接近 Flutter Framework 的實現:經過 rootLayer
一級一級 append
構建出Layer
樹,而 rootLayer
調用 addToScene
方法後,由於會執行 addChildrenToScene
方法,從而往下執行 child Layer
的 addToScene
。
import 'dart:ui' as ui;
void main() {
ui.window.onBeginFrame = beginFrame;
ui.window.scheduleFrame();
}
void beginFrame(Duration timeStamp) {
final double devicePixelRatio = ui.window.devicePixelRatio;
///建立一個畫板
final ui.PictureRecorder recorder = ui.PictureRecorder();
///基於畫板建立一個 Canvas
final ui.Canvas canvas = ui.Canvas(recorder);
canvas.scale(devicePixelRatio, devicePixelRatio);
var centerX = ui.window.physicalSize.width / 2.0;
var centerY = ui.window.physicalSize.height / 2.0;
///畫一個 100 的劇中藍色
canvas.drawRect(Rect.fromCenter(center: Offset.zero, width: 100, height: 100),
new Paint()..color = Colors.blue);
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();
OffsetLayer rootLayer = new OffsetLayer();
OffsetLayer offsetLayer = new OffsetLayer(offset: Offset(centerX, centerY));
rootLayer.append(offsetLayer);
PictureLayer pictureLayer = new PictureLayer(Rect.zero);
pictureLayer.picture = recorder.endRecording();
offsetLayer.append(pictureLayer);
rootLayer.addToScene(sceneBuilder);
ui.window.render(sceneBuilder.build());
}
複製代碼
這裏額外介紹下 Flutter 中常見的 Layer
,以下圖所示,通常 Flutter 中 Layer
能夠分爲 ContainerLayer
和非 ContainerLayer
。
ContainerLayer
是能夠具有子節點,也就是帶有 append
方法,大體能夠分爲:
OffsetLayer
/TransformLayer
);OpacityLayer
)ClipRectLayer
/ClipRRectLayer
/ClipPathLayer
);PhysicalModelLayer
)爲何這些 Layer
須要是 ContainerLayer
?由於這些 Layer
都是一些像素合成的操做,其自己是不具有「描繪」控件的能力,就如前面的藍色小方塊例子同樣,若是要呈現畫面通常須要和 PictureLayer
結合。
好比
ClipRRect
控件的RenderClipRRect
內部,在pushClipRRect
時能夠會建立ClipRRectLayer
,而新建立的ClipRRectLayer
會經過appendLayer
方法觸發append
操做添加爲父Layer
的子節點。
而非 ContainerLayer
通常不具有子節點,好比:
PictureLayer
是用於繪製畫面,Flutter 上的控件基本是繪製在這上面;TextureLayer
是用於外界紋理,好比視頻播放或者攝像頭數據;PlatformViewLayer
是用於 iOS 上 PlatformView
相關嵌入紋理的使用;舉個例子,控件繪製時的 Canvas
來源於 PaintingContext
, 而以下代碼所示 PaintingContext
經過 _repaintCompositedChild
執行繪製後獲得的 Picture
最後就是提交給所在的 PictureLayer.picture
。
void stopRecordingIfNeeded() {
if (!_isRecording)
return;
_currentLayer.picture = _recorder.endRecording();
_currentLayer = null;
_recorder = null;
_canvas = null;
}
複製代碼
瞭解完 Layer
是如何提交繪製後,接下來介紹的就是 Layer
的刷新和複用。
咱們知道當 RenderObject
的 isRepaintBoundary
爲 ture
時,Flutter Framework 就會自動建立一個 OffsetLayer
來「承載」這片區域,而 Layer
內部的畫面更新通常不會影響到其餘 Layer
。
那 Layer
是如何更新?這就涉及了 Layer
內部的 markNeedsAddToScene
和 updateSubtreeNeedsAddToScene
這兩個方法。
以下代碼所示,markNeedsAddToScene
方法其實就是把 Layer
內的 _needsAddToScene
標記爲 true
; 而 updateSubtreeNeedsAddToScene
方法就是遍歷全部 child Layer
,經過遞歸調用 updateSubtreeNeedsAddToScene()
判斷是否有 child
須要 _needsAddToScene
,若是是那就把本身也標記爲 true
。
@protected
@visibleForTesting
void markNeedsAddToScene() {
// Already marked. Short-circuit.
if (_needsAddToScene) {
return;
}
_needsAddToScene = true;
}
@override
void updateSubtreeNeedsAddToScene() {
super.updateSubtreeNeedsAddToScene();
Layer child = firstChild;
while (child != null) {
child.updateSubtreeNeedsAddToScene();
_needsAddToScene = _needsAddToScene || child._needsAddToScene;
child = child.nextSibling;
}
}
複製代碼
是否是和 setState
調用 markNeedsBuild
把本身標誌爲 _dirty
很像?當 _needsAddToScene
等於 true
時,對應 Layer
的 addToScene
纔會被調用;而當 Layer
的 _needsAddToScene
爲 false
且 _engineLayer
不爲空時就觸發 Layer
的複用。
void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {
if (!_needsAddToScene && _engineLayer != null) {
builder.addRetained(_engineLayer);
return;
}
addToScene(builder);
_needsAddToScene = false;
}
複製代碼
是的,當一個 Layer
的 _needsAddToScene
爲 false
時 代表了本身不須要更新,那這個 Layer
的 EngineLayer
又存在,那 就能夠被複用。舉個例子:當一個新的頁面打開時,底部的頁面並無發生變化時,它只是參與畫面的合成,因此對於底部頁面來講它 「Layer
」 是能夠直接被複用參與繪製。
那 markNeedsAddToScene
在何時會被調用?
以下圖所示,當 Layer
子的參數,好比: PictureLayer
的 picture
、OffsetLayer
的 offset
發生變化時,Layer
就會主動調用 markNeedsAddToScene
標記本身爲「髒」區域。另外當 Layer
的 engineLayer
發生變化時,就會嘗試觸發父節點的 Layer
調用 markNeedsAddToScene
,這樣父節點也會對應產生變化。
@protected
set engineLayer(ui.EngineLayer value) {
_engineLayer = value;
if (!alwaysNeedsAddToScene) {
if (parent != null && !parent.alwaysNeedsAddToScene) {
parent.markNeedsAddToScene();
}
}
}
複製代碼
而 updateSubtreeNeedsAddToScene
是在 buildScene
的時候觸發,在 addToScene
以前調用 updateSubtreeNeedsAddToScene
再次判斷 child 節點,從而肯定是否須要發生改變。
ui.Scene buildScene(ui.SceneBuilder builder) {
List<PictureLayer> temporaryLayers;
assert(() {
if (debugCheckElevationsEnabled) {
temporaryLayers = _debugCheckElevations();
}
return true;
}());
updateSubtreeNeedsAddToScene();
addToScene(builder);
_needsAddToScene = false;
final ui.Scene scene = builder.build();
return scene;
}
複製代碼
最後迴歸到 Flutter Framework ,在 Flutter Framework 中 _window.render
是在 RenderView
的 compositeFrame
方法中被調用;而 RenderView
是在RendererBinding
的 initRenderView
被初始化;initRenderView
是在 initInstances
時被調用,也就是 runApp
的時候。
簡單來講就是:runApp
的時候建立了 RenderView
,而且 RenderView
內部的 compositeFrame
就是經過 _window.render
來提交 Layer
的繪製。
void compositeFrame() {
Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
_window.render(scene);
scene.dispose();
assert(() {
if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
return true;
}());
} finally {
Timeline.finishSync();
}
}
複製代碼
因此 runApp
的時候 Flutter 建立了 RenderView
,而且在 Window
的 drawFrame
方法中調用了 renderView.compositeFrame();
提交了繪製,而 RenderView
做爲根節點,它攜帶的 rootLayer
爲 OffsetLayer
的子類 TransformLayer
,屬因而 Flutter 中 Layer
的根節點。
這裏舉個例子,以下圖所示是一個簡單的不規範代碼,運行後出現的結果是一個黑色空白頁面,這裏咱們經過 debugDumpLayerTree
方法打印出 Layer
的機構。
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
new Future.delayed(Duration(seconds: 1), () {
debugDumpLayerTree();
});
return MaterialApp(
title: 'GSY Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Container(),
//routes: routers,
);
}
}
複製代碼
打印出的結果以下 LOG 所示,正如前面所說 TransformLayer
做爲 rooterLayer
它的 owner
是 RenderView
,而後它有兩個 child 節點: child1 OffsetLayer
和 child2 PictureLayer
。
默認狀況下由於
Layer
的造成機制(isRepaintBoundary
爲ture
自動建立一個OffsetLayer
)和Canvas
繪製須要,至少會有一個OffsetLayer
和PictureLayer
。
I/flutter (32494): TransformLayer#f8fa5
I/flutter (32494): │ owner: RenderView#2d51e
I/flutter (32494): │ creator: [root]
I/flutter (32494): │ offset: Offset(0.0, 0.0)
I/flutter (32494): │ transform:
I/flutter (32494): │ [0] 2.8,0.0,0.0,0.0
I/flutter (32494): │ [1] 0.0,2.8,0.0,0.0
I/flutter (32494): │ [2] 0.0,0.0,1.0,0.0
I/flutter (32494): │ [3] 0.0,0.0,0.0,1.0
I/flutter (32494): │
I/flutter (32494): ├─child 1: OffsetLayer#4503b
I/flutter (32494): │ │ creator: RepaintBoundary ← _FocusMarker ← Semantics ← FocusScope
I/flutter (32494): │ │ ← PageStorage ← Offstage ← _ModalScopeStatus ←
I/flutter (32494): │ │ _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#e1be1]
I/flutter (32494): │ │ ← _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#95107] ←
I/flutter (32494): │ │ Stack ← _Theatre ←
I/flutter (32494): │ │ Overlay-[LabeledGlobalKey<OverlayState>#ceb36] ← ⋯
I/flutter (32494): │ │ offset: Offset(0.0, 0.0)
I/flutter (32494): │ │
I/flutter (32494): │ └─child 1: OffsetLayer#e8309
I/flutter (32494): │ creator: RepaintBoundary-[GlobalKey#bbad8] ← IgnorePointer ←
I/flutter (32494): │ FadeTransition ← FractionalTranslation ← SlideTransition ←
I/flutter (32494): │ _FadeUpwardsPageTransition ← AnimatedBuilder ← RepaintBoundary
I/flutter (32494): │ ← _FocusMarker ← Semantics ← FocusScope ← PageStorage ← ⋯
I/flutter (32494): │ offset: Offset(0.0, 0.0)
I/flutter (32494): │
I/flutter (32494): └─child 2: PictureLayer#be4f1
I/flutter (32494): paint bounds: Rect.fromLTRB(0.0, 0.0, 1080.0, 2030.0)
複製代碼
根據上述 LOG 所示,首先看:
OffsetLayer
的 creator
是 RepaintBoundary
,而其來源是 Overlay
,咱們知道 Flutter 中能夠經過 Overlay
作全局懸浮控件,而 Overlay
就是在 MaterialApp
的 Navigator
中建立,而且它是一個獨立的Layer
;OffsetLayer
的 child 是 PageStorage
,PageStorage
是經過 Route
產生的,也便是默認的路由第一個頁面。因此如今知道爲何 Overlay
能夠在 MaterialApp
的全部路由頁面下全局懸浮顯示了吧。
以下代碼所示,再本來代碼的基礎上增長 Scaffold
後繼續執行 debugDumpLayerTree
。
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
new Future.delayed(Duration(seconds: 1), () {
debugDumpLayerTree();
});
return MaterialApp(
title: 'GSY Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: Container(),
),
//routes: routers,
);
}
}
複製代碼
能夠看到這裏多了一個 PhysicalModelLayer
和 PictureLayer
,PhysicalModelLayer
是用於調試時顯示調試蒙層的,好比打開蒙層後能夠看到各類顏色的標註,若是不須要能夠設置 debugDisablePhysicalShapeLayers
,而以後的 PictureLayer
也是用於繪製。
I/flutter (32494): TransformLayer#ac14b
I/flutter (32494): │ owner: RenderView#f5ecc
I/flutter (32494): │ creator: [root]
I/flutter (32494): │ offset: Offset(0.0, 0.0)
I/flutter (32494): │ transform:
I/flutter (32494): │ [0] 2.8,0.0,0.0,0.0
I/flutter (32494): │ [1] 0.0,2.8,0.0,0.0
I/flutter (32494): │ [2] 0.0,0.0,1.0,0.0
I/flutter (32494): │ [3] 0.0,0.0,0.0,1.0
I/flutter (32494): │
I/flutter (32494): ├─child 1: OffsetLayer#c0128
I/flutter (32494): │ │ creator: RepaintBoundary ← _FocusMarker ← Semantics ← FocusScope
I/flutter (32494): │ │ ← PageStorage ← Offstage ← _ModalScopeStatus ←
I/flutter (32494): │ │ _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#fe143]
I/flutter (32494): │ │ ← _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#9cb60] ←
I/flutter (32494): │ │ Stack ← _Theatre ←
I/flutter (32494): │ │ Overlay-[LabeledGlobalKey<OverlayState>#ee455] ← ⋯
I/flutter (32494): │ │ offset: Offset(0.0, 0.0)
I/flutter (32494): │ │
I/flutter (32494): │ └─child 1: OffsetLayer#fb2a6
I/flutter (32494): │ │ creator: RepaintBoundary-[GlobalKey#fd46b] ← IgnorePointer ←
I/flutter (32494): │ │ FadeTransition ← FractionalTranslation ← SlideTransition ←
I/flutter (32494): │ │ _FadeUpwardsPageTransition ← AnimatedBuilder ← RepaintBoundary
I/flutter (32494): │ │ ← _FocusMarker ← Semantics ← FocusScope ← PageStorage ← ⋯
I/flutter (32494): │ │ offset: Offset(0.0, 0.0)
I/flutter (32494): │ │
I/flutter (32494): │ └─child 1: PhysicalModelLayer#f1460
I/flutter (32494): │ │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
I/flutter (32494): │ │ PrimaryScrollController ← _ScaffoldScope ← Scaffold ← Semantics
I/flutter (32494): │ │ ← Builder ← RepaintBoundary-[GlobalKey#fd46b] ← IgnorePointer ←
I/flutter (32494): │ │ FadeTransition ← FractionalTranslation ← ⋯
I/flutter (32494): │ │ elevation: 0.0
I/flutter (32494): │ │ color: Color(0xfffafafa)
I/flutter (32494): │ │
I/flutter (32494): │ └─child 1: PictureLayer#f800f
I/flutter (32494): │ paint bounds: Rect.fromLTRB(0.0, 0.0, 392.7, 738.2)
I/flutter (32494): │
I/flutter (32494): └─child 2: PictureLayer#af14d
I/flutter (32494): paint bounds: Rect.fromLTRB(0.0, 0.0, 1080.0, 2030.0)
I/flutter (32494):
複製代碼
最後經過再使用 Navigator
跳到另一個頁面,再新頁面打印 Layer
樹,能夠看到又能夠多了個 PictureLayer
、AnnotatedRegionLayer
和 TransformLayer
: 其中多了的 AnnotatedRegionLayer
是用於處理新頁面頂部狀態欄的顯示效果。
I/flutter (32494): TransformLayer#12e21
I/flutter (32494): │ owner: RenderView#aa5c7
I/flutter (32494): │ creator: [root]
I/flutter (32494): │ offset: Offset(0.0, 0.0)
I/flutter (32494): │ transform:
I/flutter (32494): │ [0] 2.8,0.0,0.0,0.0
I/flutter (32494): │ [1] 0.0,2.8,0.0,0.0
I/flutter (32494): │ [2] 0.0,0.0,1.0,0.0
I/flutter (32494): │ [3] 0.0,0.0,0.0,1.0
I/flutter (32494): │
I/flutter (32494): ├─child 1: OffsetLayer#fc176
I/flutter (32494): │ │ creator: RepaintBoundary ← _FocusMarker ← Semantics ← FocusScope
I/flutter (32494): │ │ ← PageStorage ← Offstage ← _ModalScopeStatus ←
I/flutter (32494): │ │ _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#43140]
I/flutter (32494): │ │ ← _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#46f19] ←
I/flutter (32494): │ │ Stack ← _Theatre ←
I/flutter (32494): │ │ Overlay-[LabeledGlobalKey<OverlayState>#af6f4] ← ⋯
I/flutter (32494): │ │ offset: Offset(0.0, 0.0)
I/flutter (32494): │ │
I/flutter (32494): │ └─child 1: OffsetLayer#b6e14
I/flutter (32494): │ │ creator: RepaintBoundary-[GlobalKey#0ce90] ← IgnorePointer ←
I/flutter (32494): │ │ FadeTransition ← FractionalTranslation ← SlideTransition ←
I/flutter (32494): │ │ _FadeUpwardsPageTransition ← AnimatedBuilder ← RepaintBoundary
I/flutter (32494): │ │ ← _FocusMarker ← Semantics ← FocusScope ← PageStorage ← ⋯
I/flutter (32494): │ │ offset: Offset(0.0, 0.0)
I/flutter (32494): │ │
I/flutter (32494): │ └─child 1: PhysicalModelLayer#4fdc6
I/flutter (32494): │ │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
I/flutter (32494): │ │ PrimaryScrollController ← _ScaffoldScope ← Scaffold ←
I/flutter (32494): │ │ ClipDemoPage ← Semantics ← Builder ←
I/flutter (32494): │ │ RepaintBoundary-[GlobalKey#0ce90] ← IgnorePointer ←
I/flutter (32494): │ │ FadeTransition ← ⋯
I/flutter (32494): │ │ elevation: 0.0
I/flutter (32494): │ │ color: Color(0xfffafafa)
I/flutter (32494): │ │
I/flutter (32494): │ ├─child 1: PictureLayer#6ee26
I/flutter (32494): │ │ paint bounds: Rect.fromLTRB(0.0, 0.0, 392.7, 738.2)
I/flutter (32494): │ │
I/flutter (32494): │ ├─child 2: AnnotatedRegionLayer<SystemUiOverlayStyle>#cbeaf
I/flutter (32494): │ │ │ value: {systemNavigationBarColor: 4278190080,
I/flutter (32494): │ │ │ systemNavigationBarDividerColor: null, statusBarColor: null,
I/flutter (32494): │ │ │ statusBarBrightness: Brightness.dark, statusBarIconBrightness:
I/flutter (32494): │ │ │ Brightness.light, systemNavigationBarIconBrightness:
I/flutter (32494): │ │ │ Brightness.light}
I/flutter (32494): │ │ │ size: Size(392.7, 83.6)
I/flutter (32494): │ │ │ offset: Offset(0.0, 0.0)
I/flutter (32494): │ │ │
I/flutter (32494): │ │ └─child 1: PhysicalModelLayer#edb15
I/flutter (32494): │ │ │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
I/flutter (32494): │ │ │ AnnotatedRegion<SystemUiOverlayStyle> ← Semantics ← AppBar ←
I/flutter (32494): │ │ │ FlexibleSpaceBarSettings ← ConstrainedBox ← MediaQuery ←
I/flutter (32494): │ │ │ LayoutId-[<_ScaffoldSlot.appBar>] ← CustomMultiChildLayout ←
I/flutter (32494): │ │ │ AnimatedBuilder ← ⋯
I/flutter (32494): │ │ │ elevation: 4.0
I/flutter (32494): │ │ │ color: MaterialColor(primary value: Color(0xff2196f3))
I/flutter (32494): │ │ │
I/flutter (32494): │ │ └─child 1: PictureLayer#418ce
I/flutter (32494): │ │ paint bounds: Rect.fromLTRB(0.0, 0.0, 392.7, 83.6)
I/flutter (32494): │ │
I/flutter (32494): │ └─child 3: TransformLayer#7f867
I/flutter (32494): │ │ offset: Offset(0.0, 0.0)
I/flutter (32494): │ │ transform:
I/flutter (32494): │ │ [0] 1.0,0.0,0.0,-0.0
I/flutter (32494): │ │ [1] -0.0,1.0,0.0,0.0
I/flutter (32494): │ │ [2] 0.0,0.0,1.0,0.0
I/flutter (32494): │ │ [3] 0.0,0.0,0.0,1.0
I/flutter (32494): │ │
I/flutter (32494): │ └─child 1: PhysicalModelLayer#9f36b
I/flutter (32494): │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←
I/flutter (32494): │ │ ConstrainedBox ← _FocusMarker ← Focus ← _InputPadding ←
I/flutter (32494): │ │ Semantics ← RawMaterialButton ← KeyedSubtree-[GlobalKey#9ead9]
I/flutter (32494): │ │ ← TickerMode ← Offstage ← ⋯
I/flutter (32494): │ │ elevation: 6.0
I/flutter (32494): │ │ color: Color(0xff2196f3)
I/flutter (32494): │ │
I/flutter (32494): │ └─child 1: PictureLayer#2a074
I/flutter (32494): │ paint bounds: Rect.fromLTRB(320.7, 666.2, 376.7, 722.2)
I/flutter (32494): │
I/flutter (32494): └─child 2: PictureLayer#3d42d
I/flutter (32494): paint bounds: Rect.fromLTRB(0.0, 0.0, 1080.0, 2030.0)
I/flutter (32494):
複製代碼
因此能夠看到,Flutter 中的 Widget
在最終造成各式各樣的 Layer
,每一個 Layer
都有本身單獨的區域和功能,好比 AnnotatedRegionLayer
在新的頁面處理狀態欄顏色的變化,而這些 Layer
最終經過 SceneBuilder
轉化爲 EngineLayer
,最後提交爲 Scene
經由 Engine 繪製。
最後總結一下:Flutter Framework 的 Layer
在繪製以前,須要經歷 SceneBuinlder
的處理獲得 EngineLayer
,其實 Flutter Framework 中的 Layer
能夠理解爲 SceneBuinlder
的對象封裝,而 EngineLayer
纔是真正的 Engine 圖層 ,在以後獲得的 Scene
會被提交 Engine 繪製。
自此,第二十一篇終於結束了!(///▽///)