作為系列文章的第二十一篇,本篇将通過不一樣的角度來介紹 Flutter Framework 的整體渲染原理,深入剖析 Flutter 中構成 Layer 後的繪制流程,讓開發者對 Flutter 的渲染原理和實作邏輯有更清晰的認知。
文章彙總位址:
Flutter 完整實戰實戰系列文章專欄
Flutter 番外的世界系列文章專欄
一、Layer 相關的回顧
先回顧下,我們知道在 Flutter 中的控件會經曆
Widget
->
Element
->
RenderObject
->
Layer
這樣的變化過程,而其中
Layer
的組成由
RenderObject
中的
isRepaintBoundary
标志位決定。
當調用 時,
setState
就會往上的父節點去查找,根據
RenderObject
是否為 true,會決定是否從這裡開始往下去觸發重繪,換個說法就是:确定要更新哪些區域。
isRepaintBoundary
比如
Navigator
跳轉不同路由頁面,每個頁面内部就有一個
RepaintBoundary
控件,這個控件對應的
RenderRepaintBoundary
内的
isRepaintBoundary
标記位就是為
true
,進而路由頁面之間形成了獨立的
Layer
。
是以相關的
RenderObject
在一起組成了
Layer
,而由
Layer
構成的
Layer Tree
最後會被送出到 Flutter Engine 繪制出畫面。
那
Layer
是怎麼工作的?它的本質又是什麼? Flutter Framework
中
Layer
是如何被送出到 Engine 中?
二、Flutter Framework 中的繪制
帶着前面
Layer
的問題,我們先做個假設:如果抛開 Flutter Framework 中封裝好的控件,我們應該如何繪制出一個畫面?或者說如何建立一個
Layer
?
舉個例子,如下代碼所示,運作後可以看到一個居中顯示的 100 x 100 的藍色方塊,并且代碼裡沒有用到任何
Widget
、
RenderObject
甚至
Layer
,而是使用了
PictureRecorder
、
Canvas
、
SceneBuilder
這些相對陌生的對象完成了畫面繪制,并且在最後執行的是
window.render
。
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
。在官方的examples/layers/raw/ 下有不少類似的例子。
window.onBeginFrame = beginFrame;

可以看到 Flutter Framework 在底層繪制的最後一步是
window.render
,而如下代碼所示:
render
方法需要的參數是
Scene
對象,并且
render
方法是一個
native
方法,說明 Flutter Framework 最終送出給 Engine 的是一個
Scene
。
void render(Scene scene) native 'Window_render';
那
Scene
又是什麼?前面所說的
Layer
又在哪裡呢?它們之間又有什麼樣的關系?
三、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());
}
四、Layer 的品種
這裡額外介紹下 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
一般不具備子節點,比如:
-
是用于繪制畫面,Flutter 上的控件基本是繪制在這上面;PictureLayer
-
是用于外界紋理,比如視訊播放或者攝像頭資料;TextureLayer
-
是用于 iOS 上PlatformViewLayer
相關嵌入紋理的使用;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
是如何送出繪制後,接下來介紹的就是
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 的 Layer 構成
最後回歸到 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
,我們知道 Flutter 中可以通過Overlay
做全局懸浮控件,而Overlay
就是在Overlay
的MaterialApp
中建立,并且它是一個獨立的Navigator
;Layer
- 而
的 child 是OffsetLayer
,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
後
AppBar
的陰影會消失,而之後的
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 繪制。
資源推薦
- Github :github.com/CarGuo
- 開源 Flutter 完整項目:github.com/CarGuo/GSYG…
- 開源 Flutter 多案例學習型項目: github.com/CarGuo/GSYF…
- 開源 Fluttre 實戰電子書項目:github.com/CarGuo/GSYF…
- 開源 React Native 項目:github.com/CarGuo/GSYG…