Overview of Content
本篇文章將帶你深入了解 Flutter 的三顆重要樹結構:Widget
、Element
與 RenderObject
。透過清晰的章節分解,我們將探討元件的包裹與繼承關係,從 Widget 樹如何連接 Element 實例,再到 RenderObject 的工作原理,逐步揭開這些複雜結構之間的互動。
你將學到 Widget 元件的初始化過程、掛載流程,並理解每一個 Element 在生命週期中的角色與運作方式。如果你正在學習 Flutter,這篇文章將幫助你掌握底層架構,提升開發效率!
關於 Flutter 的渲染機制,主要是透過三棵樹來進行的,分別是 1 最常操控的 Widget、2 包在中間銜接渲染的 Element、3 真正進行渲染的 RenderObject
這三顆樹是 UI 架構的核心概念,它們共同協作來構建、描述和渲染用戶界面
以下使用的 Flutter 版本為
3.22.2
Element 元件關係:包裹、繼承、樹關係
首先我們先從我們開發 Flutter 時最常接觸到的 Widget(也就是元件)開始看它們之間的依賴關係,進發現我們平常所用的 Widget 的「包裹」、「繼承」與「樹」關係
Widget 樹:包裹、樹關係
● 直接創建一個 Flutter 專案,它會有基本的 Demo 示例,從 MyApp 類開始分析 Widget 樹狀圖… (這裡我們省略一步一步分析,直接看最表面的 Widget 關係)
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
從以上範例中的分析,這裡我們可以先觀察最常使用到的 Widget 建構,它們是一層一層包裹(Wrapper
)的結構,並且其中存在著「多元樹 🌲」的關係
關係如下圖所示
Widget 繼承關係:發現 Element
● 上面有使用到的組件有 Scaffold
、AppBar
、Text
、Center
、Colume
、Icon
... 等等的組件,現在我們來看這些組件的「繼承關係」,會發現它們 最終都會歸到 Widget 這個抽象類中
而 Widget 類的重點方法有四個,如下表
Widget 重點方法 | 說明 |
---|---|
createElement() → Element | 創建並返回與此 Widget 對應的 Element。這是每個 Widget 都必須實現的方法,因為 Flutter 框架通過 Element 來管理 Widget 的佈局和繪製 |
==(Object other) → bool | 比較兩個 Widget 是否相等。通常是通過比對 runtimeType 和 key 來確定相等性 |
hashCode → int | 返回該 Widget 的哈希值,這是與 == 方法相配合使用的,用於在集合(如 HashMap )中有效地比較和存儲 Widget |
canUpdate(Widget oldWidget, Widget newWidget) → bool | 檢查是否可以更新一個現有的 Widget,這通常用於在 Widget 樹更新時決定是否需要重建或只是更新現有的 Element |
A. createElement()
:此方法是 Widget 最核心的部分,負責將 Widget 與 Element 系統進行對接
這裡我們會發現
createElement()
方法,這是第一次窺見到 Element(三顆樹中其中一個)… 該方法會讓 Widget 與 Element 產生關係
B. == 與 hashCode
:這兩個方法用於比較和管理 Widget 的唯一性,特別是在有狀態管理和 Widget 重建過程中
C. canUpdate()
:這個方法決定了當 Widget 樹發生變化時,是否應該替換現有的 Widget,還是只進行更新
認識三顆樹:Widget、Element、RenderObject
在 Flutter 框架中,Widget 樹、Element 樹 和 RenderObject 樹 是 UI 架構的核心概念,它們共同協作來構建、描述和渲染用戶界面
認識 Widget 樹:連接 Element 實例
● 認識 Widget 樹:
Widget 是 Flutter 中最基本的構建單元,描述了應用程序的外觀和結構,並且 每一個 Widget 都是一個靜態的、不可變的配置
● Widget 是一個抽象類,並且有一個一定要實現的抽象方法 createElement,該函數會返回一個 Element 類的實例(這就是三顆樹的其中一個)
這樣就可以知道 Widget 的另外一個重點功能是在創建 Element 物件
而我們在寫 Flutter 應用的時候其實是不常碰到 Element,而是透過創建各種 Widget 來建構畫面,而其實 Widget 是讓我們與 Element 做隔離的元件
看看 Widget 的繼承關係,可以知道 所有繼承 Widget 的類都需要實現 Element 類,所以 Element 與 Widget 是一個一個對應的,像是…
A. StatefulWidget → StatefulElement
B. StatelessWidget → StatelessElement
C. RenderObjectWidget → RenderObjectElement
不同 Widget 的 Element 實作
● 再來看看 Element 與各種各自實作 Element 的繼承關係,這邊除了有常見的 StatefulElement
& StatelessElement
,還有看見另外一個 RenderObjectElement
類;接下來會做一些簡單的介紹…
A. StatefulElement 類
● 使用場景: 用於管理與 StatefulWidget 關聯的 Element
● 主要功能:
● 當 StatefulWidget 重建時,StatefulElement 保持 State 不變,這樣即使 Widget 被重建,狀態仍然保留(例如:計數器、表單中的輸入數據等)
● StatefulElement 與一個 State 物件相關聯,這個 State 是可變的,並且在 Widget 的生命週期中持續存在
也就是我們平常在更新 Widget 畫面時,會使用的
setState
方法,就是用來刷新 StatefulElement 的 State!
● StatefulElement 通過調用 State 的 build()
方法來構建和更新其子樹
B. StatelessElement 類
● 使用場景: 用於管理與 StatelessWidget 關聯的 Element
● 主要功能:
● StatelessElement 不會保留任何狀態,當它的父 Widget 樹更新時,只會調用 StatelessWidget 的 build()
方法來重建 UI
也就是 StatelessElement 不具有 State,所以重新建構時是重新建構 Element
● 由於 StatelessWidget 是不可變的,它們每次更新時都會丟棄舊的元素並使用新元素重新構建(也就是缺乏 State,所以不會保留任何狀態)
StatelessElement 不關心狀態的變化,因為 StatelessWidget 只關注不可變的 UI 呈現
C. RenderObjectElement 類(跟佈局更相關的 Element)
● 使用場景: 用於管理與 RenderObjectWidget 關聯的 Element
● 主要功能:
● RenderObjectElement 主要負責創建和管理 RenderObject,這是用於處理佈局和繪製的物件
● 當 Widget 發生變化時,RenderObjectElement 決定如何更新或重建其對應的 RenderObject
● 它與
StatefulElement
和StatelessElement
不同,RenderObjectElement 的重點是「處理佈局、尺寸和繪製邏輯」,而不是單純地重建子 Widget 樹(請注意這裡說的是邏輯… 至於繪製則是交給RenderObject
類)它通常會用於像
Container
、CustomPaint
等需要處理具體佈局和繪製的 Widget
認識 RenderObject 樹:Widget 與 RenderObject 關係
● 我們知道一個 Widget 通常是依賴著其他 Widget 建構 (初始專案的 MyApp 就是這樣一個 Widget),而現在我們就來找找 Flutter SDK 提供的原生 Widget 最終是如何實現的
以 Text
、Column
、Center
、Icon
、Image
Widget… 來做觀察 (注意顏色、線條,個代表了不同意義),可以看到最終元件都會與於 RenderObject 產生關係(大部分都會透過 Element 與 RenderObject 產生關係)
● 可以把 Widget 看成是一個 設計稿,將稿 (Widget) 完成後就不會再改變,這時就只需要放入對應的元件(
RenderObject
) 就可以生成不同的 UI 介面RenderObject 才是真正渲染畫面的物件
● 那 RenderObject 跟 RenderObjectElement 是同樣含義嗎? 並不是~ 兩者主要負責的責任不同
A. RenderObject 類:負責繪製
RenderObject 是一個專門負責處理 佈局
、繪製
、命中測試
和 邊界處理
的物件,「它是渲染層的核心」,負責實際在螢幕上繪製和排列可視的內容
RenderObject 決定 Widget 應該如何呈現在螢幕上,它處理像素繪製、邊界計算以及 Widget 的尺寸與位置
B. RenderObjectElement 類:負責連接
RenderObjectElement 是 Element 的一個具體子類,「它負責將 Widget 和 RenderObject 連接起來」…
進一步認識 RenderObject
並且它管理 Widget 和 RenderObject 之間的關係,並確保 RenderObject 正確地反映 Widget 的狀態
● RenderObject 是一個大家族,像是 RenderView
、RenderBox
而其中 RenderBox 有許多的衍生類(像是 RenderWrap
、RenderStack
、RednerFlex
… 等等),簡單功能介紹如下表
類別名稱 | 簡介 |
---|---|
RenderObject | Flutter 渲染系統的基礎類,負責定義佈局、繪製、命中測試等基本功能,其他渲染對象都是從它繼承而來 |
RenderBox | RenderObject 的子類,定義了 2D 矩形盒模型的佈局規則,許多常見的渲染對象(如 RenderFlex, RenderStack)都繼承自 RenderBox |
RenderView | 渲染樹的根節點,表示應用程式的「視圖」,它負責與 Flutter 引擎交互,並決定渲染區域的大小和邊界 |
RenderWrap | 一種專門的 RenderBox,負責在可用空間內將子對象按行或列排列,並根據需要進行換行,適合用於實現自適應佈局 |
RenderStack | 另一種 RenderBox,允許子對象重疊繪製,類似於 HTML 中的 z-index,用於堆疊式佈局 |
RenderFlex | 定義了一個彈性盒模型,允許將子對象水平或垂直排列,並根據可用空間分配每個子對象的大小,類似於 CSS 中的 flexbox |
RenderFlow | 一個更加靈活和強大的佈局類別,它允許開發者自定義佈局規則,通過覆寫佈局邏輯來控制子對象的排列方式 |
RenderImage | 專門用來繪製圖像的渲染對象,負責處理圖像的佈局和繪製邏輯,常見於顯示圖片的 Widget,如 Image |
從上表的介紹中,我們可以很清楚的知道 RenderBox 的子類負責各式各樣的佈局,而 RenderView 負責個別元件的渲染
● 再來看看 RenderObject 中幾個比較重要的方法(它的方法太多了,這邊我們只挑幾種介紹),從這些方法就可以看出 RenderObject 可布局、繪畫、響應事件
RenderObject 方法 | 說明 |
---|---|
layout | 就像是 Android's onLayout,用來 對元件布局 |
paint | 就像是 Android's onDraw,用來 繪製元件 |
handleEvent | 就像是 Android's dispatchTouchEvent ,用來處理事件 |
看看 SDK 提供的 Widget 是否有重寫 layout、paint、handleEvent 方法,並且每個 Widget 的著重重點在哪裡
A. 可見元件 TextWidget:藉由上圖,我們這邊直接看 TextWidget 對應的 RenderBox 的子類 RenderParagraph
我們可以看到 RenderParagraph#paint 方法在繪製時主要是透過 TextPainter
去繪製文字,而繪製是繪製在 Canvas 之上
class RenderParagraph extends RenderBox with ... {
final TextPainter _textPainter;
@override
void paint(PaintingContext context, Offset offset) {
_layoutTextWithConstraints(constraints);
... 省略
// 查看 paint 方法
_textPainter.paint(context.canvas, offset);
... 省略
}
}
// ----------------------------------------------------------
class TextPainter {
void paint(Canvas canvas, Offset offset) {
assert(... 略);
// 使用 canvas.drawParagraph 繪製
canvas.drawParagraph(_paragraph!, offset);
}
}
B. 可見元件 ImageWidget:藉由上圖,我們這邊直接看 ImageWidget 對應的 RenderBox 的子類 RenderImage
我們可以看到 RenderImage#paint 方法在繪製時主要是透過 paintImage
方法去繪製圖片,繪製同樣是繪製在 Canvas 之上
// rendering/image.dart
class RenderImage extends RenderBox {
@override
void paint(PaintingContext context, Offset offset) {
... 略
// 查看 paintImage 方法
paintImage(
canvas: context.canvas,
rect: offset & size,
image: _image!,
... 略
);
}
}
// -------------------------------------------------------------
// decoration_image.dart
void paintImage({
required Canvas canvas,
required Rect rect,
required ui.Image image,
... 略
}) {
// 這裡就可以證明是用 canvas 來繪畫 Image
canvas.translate(0.0, -dy);
canvas.scale(1.0, -1.0);
canvas.translate(0.0, dy);
}
C. 布局元件 Column:這裡 paint 就不是重點,重點在 layout
class RenderFlex extends RenderBox with ... {
@override
void paint(PaintingContext context, Offset offset) {
...
switch (_direction) {
case Axis.horizontal:
overflowChildRect = Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0);
break;
case Axis.vertical:
overflowChildRect = Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow);
break;
}
paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints);
}
}
// ---------------------------------------
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
void layout(Constraints constraints, { bool parentUsesSize = false }) {
... 略
}
}
認識 Element 樹:Element 重點成員、生命週期
● Element 是 Widget 的實例化對象,它是 Widget 和 RenderObject 之間的橋樑;Element 樹負責管理 Widget 的渲染、狀態維護,以及與 RenderObject 之間的交互(仲介的角色)
而 Element 的重點成員如下表所示
Element 成員 | 功能 |
---|---|
_dirty | 是否更新渲染元件 |
_owner | 負責管理這個 Element 樹的生命周期和構建過程。每個 Element 都與一個 BuildOwner 相關聯 |
_lifecycleState | 標記 Element 的當前生命周期狀態,例如 active 或 inactive,這有助於追蹤 Element 是否在樹中活躍,還是即將被移除 |
_parent | 父類(不是傳統意義上的繼承,而是在其之上的 Element 物件實例),這可以看出 Element 是 Tree 結構 |
_renderObject | 對應的 RenderObject,如果 Widget 需要渲染,Element 會持有一個 RenderObject 物件,該物件負責實際的佈局和繪製 |
_depth | 該 Element 在樹中的深度,這有助於確定 Element 的更新順序,越深的元素會在重建過程中後更新 |
_buildContext | 每個 Element 都實現了 BuildContext,通過它可以訪問與 Widget 相關的依賴上下文 |
abstract class Element extends DiagnosticableTree implements BuildContext {
bool _dirty = true;
BuildOwner? _owner;
Element? _parent;
// 生命狀態
_ElementLifecycle _lifecycleState = _ElementLifecycle.initial;
Element(Widget widget)
: assert(widget != null),
_widget = widget;
}
● 接著我們要知道 Element 也有自己的「生命週期, _lifecycleState
」,其生命週期有下面幾種階段(就像是 Android 的 Activity 一般)
Element 成員 | 功能 |
---|---|
_dirty | 是否更新渲染元件 |
_owner | 負責管理這個 Element 樹的生命周期和構建過程。每個 Element 都與一個 BuildOwner 相關聯 |
_lifecycleState | 標記 Element 的當前生命周期狀態,例如 active 或 inactive,這有助於追蹤 Element 是否在樹中活躍,還是即將被移除 |
_parent | 父類(不是傳統意義上的繼承,而是在其之上的 Element 物件實例),這可以看出 Element 是 Tree 結構 |
_renderObject | 對應的 RenderObject,如果 Widget 需要渲染,Element 會持有一個 RenderObject 物件,該物件負責實際的佈局和繪製 |
_depth | 該 Element 在樹中的深度,這有助於確定 Element 的更新順序,越深的元素會在重建過程中後更新 |
_buildContext | 每個 Element 都實現了 BuildContext,通過它可以訪問與 Widget 相關的依賴上下文 |
enum _ElementLifecycle {
initial, // 初始化
active, // 激活
inactive, // 未激活
defunct, // 死亡
}
而生命週期的變動,是可以通過幾種方法的調用來達到… 舉例:像是通過調用 Element#mount
方法,就可以激活該元件到 active 狀態
mount
之後會再詳細介紹
// Element
void mount(Element? parent, Object? newSlot) {
assert(_lifecycleState == _ElementLifecycle.initial);
...
// 將 Widget 改為激活狀態
_lifecycleState = _ElementLifecycle.active;
...
}
三棵樹的根源
Flutter App 在初始化的過程就會接觸到三棵樹(Widget
、Element
、RenderObject
)的根源,知道這些根源我們就可以大概地去瞭解 Flutter App 是如何與 UI 框架的最初根源,好方便我們之後追尋;
而這裡我們要找的就是三棵樹的根源
找 Element 根類:RootElement 的創建
● 這邊我透過在 Element#mount
方法中添加一個 print
方法,來看看 element tree 在掛載時的結構層級
// framework.dart
void mount(Element? parent, Object? newSlot) {
...
_parent = parent;
print("_parent: ${parent.runtimeType}");
...
}
如下圖所見
由於這個 Tree 太長,我就不全部列出了,沒有太大的意義,重點我們可以看到 Element 的根物件的類是 RootElement
● 或是你在 Debug 模式下把斷點設置在 MyApp#
build
函數中,並在進入斷點時,查看_parent
成員,一直點到最底層也可以看到跟物件為RootElement
● 接著,我們要找尋 RootElement 的持有者(是誰創建的),可以透過以下方法
A. 先把斷點下在 RootElement 類的建構函數,在 Debug 模式運行 APP
B. 在進入斷點後,單步執行,就可以發現 RootElement 是由 RootWidget 類創建
找 RanderObject 根類
● 接續上個小節,在 Debug 模式下,我們透過查看 Element 的根結點類(RootElement
)的 _renderObject
成員
就可以發現負責渲染畫面的 渲染樹(RanderObject
)的根節點就是 _ReusableRenderView
,而開啟後會發現 _ReusableRenderView
就是繼承於 RenderView 類
而
_ReusableRenderView
被創建的時機則是在runApp
初始化的過程中,下面小節會再介紹
runApp 初始化、建構
我們來分析 runApp()
這個函數:從這個函數我們往下看,就可以看到三棵樹的初始化過程做了什麼事情,並且清楚的知道 Flutter App 是如何初始化應用並與 UI 框架產生關聯的
void runApp(Widget app) {
final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
_runWidget(binding.wrapWithDefaultView(app), binding, 'runApp');
}
簡單分析 WidgetsFlutterBinding
● WidgetsFlutterBinding
類是連接 Flutter widget、Flutter engine 的樞紐,其有許多種功能… 這邊我們簡單地進入源碼看一下 WidgetsFlutterBinding
類就可以發現幾件事情
● WidgetsFlutterBinding 類的實例是一個全局單例
● 它透過混合繼承的方式(with
)讓 WidgetsFlutterBinding
有以下功能
A. GestureBinding 手勢系統
B. SchedulerBinding 調度系統
C. ServicesBinding 綁定平台消息通道
D. PaintingBinding 繪製
E. WidgetsBinding 連接組件 & 引擎
// binding.dart
static WidgetsBinding? get instance => _instance;
static WidgetsBinding? _instance;
class WidgetsFlutterBinding extends BindingBase with GestureBinding,
SchedulerBinding,
ServicesBinding,
PaintingBinding,
SemanticsBinding,
RendererBinding,
WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null) // 單例
WidgetsFlutterBinding();
return WidgetsBinding.instance!;
}
}
WidgetsFlutterBinding 創建過程:with 與 super 配合
● 透過 Debug 模式下單步執行,可以發現在建構 WidgetsFlutterBinding
物件時,會先建構 BindingBase 父類,BindingBase 的建構函數會呼叫自身的 initInstances 方法,而這個方法會被其許多混合繼承類複寫
// foundation/binding.dart
abstract class BindingBase {
BindingBase() {
...
initInstances();
...
}
@protected
@mustCallSuper
void initInstances() {
assert(!_debugInitialized); // 保證該函數只會執行一次
assert(() {
_debugInitialized = true;
return true;
}());
}
}
而這些方法執行的順序就較為不同 (簡單來說 由最後一個覆寫的物件開始執行),但是!它內部有是使用到 super
關鍵字,由於 super
與 with
的配合,最終會導致 initInstances
的順序仍是由 GestureBinding
到 WidgetsBinding
A. 先執行 WidgetsBinding 類的 initInstances 方法
// widgets/binding.dart
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances(); // 呼叫到 RendererBinding
_instance = this;
...
_buildOwner = BuildOwner();
}
}
B. 再接續會執行 RendererBinding 類的 initInstances 方法
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances(); // 呼叫到 SemanticsBinding
...
}
}
C. 再接續會執行 SemanticsBinding 類的 initInstances 方法
mixin SemanticsBinding on BindingBase {
@override
void initInstances() {
super.initInstances(); // 呼叫到 PaintingBinding
...
}
}
D. 再接續會執行 PaintingBinding 類的 initInstances 方法
mixin PaintingBinding on BindingBase, ServicesBinding {
@override
void initInstances() {
super.initInstances(); // 呼叫到 SchedulerBinding
...
}
}
E. 再接續會執行 SchedulerBinding 類的 initInstances 方法
mixin SchedulerBinding on BindingBase {
@override
void initInstances() {
super.initInstances(); // 呼叫到 SemanticsBinding
...
}
}
F. 再接續會執行 SemanticsBinding 類的 initInstances 方法
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
@override
void initInstances() {
super.initInstances(); // 最終回到 BindingBase 的 initInstances 方法
// 執行完 BindingBase 的 initInstances 方法後又回到到這裡繼續執行
...
// 執行完往 SchedulerBinding 回推
}
}
●
with
與super
執行順序如同前面的說,是從最後一個覆寫得類開始執行,但在這裡透過 super 不斷執行父類方法(像是
WidgetsBinding
不斷執行 super 後會接續到GestureBinding
),最終全部執行完後又是從GestureBinding
執行到WidgetsBinding
連接根 Widget:創建 RootElement、RenderView
void runApp(Widget app) {
final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
// 查看 _runWidget 方法
_runWidget(binding.wrapWithDefaultView(app), binding, 'runApp');
}
void _runWidget(Widget app, WidgetsBinding binding, String debugEntryPoint) {
assert(binding.debugCheckZone(debugEntryPoint));
binding
..scheduleAttachRootWidget(app) // 重點分析
..scheduleWarmUpFrame();
}
● 回到上面 WidgetsFlutterBinding#ensureInitialized 完成後就會執行 scheduleAttachRootWidget
方法,這個方法就會創建 RootWidget 物件的實例,而 RootWidget 物件又會創建根元素 RootElement
// widgets/binding.dart
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
Element? _renderViewElement;
@protected
void scheduleAttachRootWidget(Widget rootWidget) { // rootWidget 就是 MyApp Widget
Timer.run(() {
attachRootWidget(rootWidget);
});
}
void attachRootWidget(Widget rootWidget) {
// 創建 RootWidget 物件實例
attachToBuildOwner(RootWidget(
debugShortDescription: '[root]',
child: rootWidget,
));
}
}
● 而這裡又要再連接到上面我們分析的 RendererBinding 類:
RendererBinding 類在進行初始化時 renderView
成員,也就是說它會在初始化時被創建,而創建的子類型就是 _ReusableRenderView
// rendering/binding.dart
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
late final RenderView renderView = _ReusableRenderView(
view: platformDispatcher.implicitView!,
);
}
但這個做法可能在新版本被遺棄,改由 renderViews 管理(因為 RendererBinding 或許需要管理多個 RenderView)
根 Widget 調用 mount:掛載 RootWidget
● 我們前面說在初始化時會創建 RootWidget
物件(根 Widget),接著我們來看 Widget 何時被掛載上去的,我們從 attachToBuildOwner
函數開始…
// widgets/binding.dart
void attachToBuildOwner(RootWidget widget) {
final bool isBootstrapFrame = rootElement == null;
_readyToProduceFrames = true;
// @ 查看 attach 方法
_rootElement = widget.attach(buildOwner!, rootElement as RootElement?);
if (isBootstrapFrame) {
SchedulerBinding.instance.ensureVisualUpdate();
}
}
A. 在 attachToBuildOwner
函數中會調用 RootWidget#attach
方法
B. 而在 RootWidget#attach
方法中又會發現它調用了 RootElement#mount
方法,剛方法會把 RootElement 掛載到 Flutter Widget 樹中進行初始化
// widgets/binding.dart
RootElement attach(BuildOwner owner, [ RootElement? element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
// 調用
element!.mount(/* parent */ null, /* slot */ null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
掛載根元素 RootElement
並且分析至此,我們可以稍為整理一下根元素所創建的樹個別是哪些,如下表所示
樹 | 真正物件 | 創建、連接時機 |
---|---|---|
Widget | RootWidget | 創建時機:runApp 調用 attachRootWidget 方法 |
Element | RootElement | 創建時機:RootWidget 類中對應的 createElement 方法 |
RenderObject | _ReusableRenderView (就是 RenderView) | 初始化 RendererBinding 時的 renderView 成員 |
連接的入口:attach 函數
● 在 attach
函數中,我們可以看到它會呼叫 createElement
方法,該方法會就會創建 Element 物件(根物件會創建 RootElement
)
RootElement attach(BuildOwner owner, [ RootElement? element ]) {
if (element == null) {
owner.lockState(() {
// 呼叫 createElement 方法,創建 RootElement 物件
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(/* parent */ null, /* slot */ null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
並且接下來我們會分析幾個重點
A. RootElement#mount
方法
B. Element#mount
方法
Element 的 mount 方法:初始化生命週期
● 我們來看看最原始的 Element#mount
方法,所有繼承於 Element 類的元素都需要呼叫這個方法… 從以下程式中,可以發現 mount
主要就是決定 Widget parent、深度、擁有者… 等等資訊
// framework.dart
abstract class Element extends DiagnosticableTree implements BuildContext {
Element? _parent;
BuildOwner? _owner;
@mustCallSuper
void mount(Element? parent, Object? newSlot) {
// 判斷生命狀態,必須是初始化
assert(_lifecycleState == _ElementLifecycle.initial);
// 判斷 widget 非空,這裡的 widget 就是 RootElement
assert(widget != null);
// parent 必須是空
assert(_parent == null);
... 其他斷言
_parent = parent; // 將傳進來的第一個參數作為 _parent,第一是傳進來是 null
_slot = newSlot;
// 切換到 active 狀態
_lifecycleState = _ElementLifecycle.active;
// 紀錄當前樹的深度
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
//
_owner = parent.owner; // 傳遞父的 owner
}
assert(owner != null);
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
}
}
RootElement 的 mount 方法:加載子佈局
● RootElement 也是繼承於 Element 類,而它自身也有複寫 mount
方法,並且在 mount
方法中呼叫私有的 _rebuild()
方法
// widgets/binding.dart
class RootElement extends Element with RootElementMixin {
@override
void mount(Element? parent, Object? newSlot) {
assert(parent == null); // We are the root!
super.mount(parent, newSlot);
_rebuild();
assert(_child != null);
super.performRebuild(); // clears the "dirty" flag
}
...
}
A. _rebuild()
方法中主要做的事情就是呼叫 Element#updateChild(...)
方法,該方法主要是返回子 Widget 元素
// widgets/binding.dart
Element? _child;
void _rebuild() {
try {
_child = updateChild(_child, (widget as RootWidget).child, /* slot */ null);
} catch (exception, stack) {
... 省略部分
}
}
B. updateChild
函數:這裡我們當成是初次加載,所以在初次呼叫時 _child
成員為 null,透過一些省略… 在帶入 updateChild
函數後我們可以簡化為以下程式來看
我們可以看到它主要是再呼叫了 inflateWidget
方法
// framework.dart
// 假設帶入的 child 為 null
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
...
final Element newChild;
if (child != null) {
...
} else {
// @ 近一步查看 inflateWidget 方法
newChild = inflateWidget(newWidget, newSlot);
}
...
return newChild;
}
C. inflateWidget
函數:該函數會先判斷 Key 是否是 GlobalKey,如果是 GlobalKey 則呼叫 updateChild
更新… 否則就呼叫 createElement
加載子類的 Element
abstract class Element extends DiagnosticableTree implements BuildContext {
// newWidget == MyApp
Element inflateWidget(Widget newWidget, Object? newSlot) {
...
try {
final Key? key = newWidget.key;
if (key is GlobalKey) {
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
...
// 若是有全域的 Key 則 updateChild
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild!;
}
}
// 若沒有全域的 Key 則呼叫 createElement
final Element newChild = newWidget.createElement();
...
// 調用 Child 自己的 Element,並且 將自己作為 parent 傳入 (mount 的第一個參數 this)
newChild.mount(this, newSlot);
assert(newChild._lifecycleState == _ElementLifecycle.active);
return newChild;
} finally {
if (isTimelineTracked) {
FlutterTimeline.finishSync();
}
}
}
這裡可以很明顯地看出一個遞迴加載 Widget 的過程
sequenceDiagram
participant Element
participant ChildElement
Element ->> ChildElement: 檢查是否是 GlobalKey
alt GlobalKey found
Element ->> Element: _retakeInactiveElement(key, newWidget)
alt Element retaken
Element ->> Element: updateChild(newChild, newWidget, newSlot)
Element ->> Element: Return updatedChild
end
else
Element ->> ChildElement: 呼叫 createElement()
ChildElement ->> Element: 創建新 Element
Element ->> Element: 加載到自身
Element ->> Element: Return newChild
end
updateChild 方法的細節:4 種狀況
● Element#updateChild 這個方法作用是返回一個 Element 對象 (作為調用者的 Child),以下針對該函數的入參 child、newWidget 來進行判斷
更新 Child 總共有四種狀況,如下表所示
child 狀態 | newWidget == null | newWidget != null |
---|---|---|
child == null | ^1^ 返回 null | ^3^ 用 inflateWidget 返回新元素 |
child != null | ^2^ 移除原來的 Child,返回 null | ^4^ 返回 Child or inflateWidget |
abstract class Element extends DiagnosticableTree implements BuildContext {
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null) // 該元素的 Child 不為空
deactivateChild(child); // 2. 移除元素的 Child
return null; // 1. 返回 null
}
final Element newChild;
if (child != null) { // 4. 該元素的 Child 不為空
bool hasSameSuperclass = true;
... 斷言
if (hasSameSuperclass && child.widget == newWidget) // 創建出來的 Widget 和自身的 Child 相同
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child; // 返回自己的 Child (省去加載)
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) { // cnaUpdate 重點
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
... 斷言
newChild = child;
} else {
deactivateChild(child);
... 斷言
newChild = inflateWidget(newWidget, newSlot);
}
} else {
// 3. inflateWidget 返回新元素
newChild = inflateWidget(newWidget, newSlot);
}
... 斷言
return newChild;
}
}
StatelessElement、StatefulElement:裝飾 & 責任鏈模式
● 從上面可以看到 Element#inflateWidget 調用了 MyApp 的 createElement 方法,而 MyApp 其實並沒有 createElement 方法,所以它會往 StatelessWidget 父類去找
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
// -------------------------------------------------------------
// framework
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key? key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
}
StatelessElement 對應 StatelessWidget
● 當 inflateWidget 調用 createElement
後會創建 StatelessElement
物件
其反應的順序簡單來說就是 ComponentElement#mount
-> _firstBuild
-> rebuild
-> performRebuild
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this); // 把自身傳入也就是 BuildContext
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
// rebuild 最終會呼叫到 build() 方法
rebuild();
}
}
abstract class ComponentElement extends Element {
ComponentElement(Widget widget) : super(widget);
...
Element? _child;
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_lifecycleState == _ElementLifecycle.active);
_firstBuild();
assert(_child != null);
}
void _firstBuild() {
rebuild();
}
@override
void performRebuild() {
Widget? built;
try {
built = build(); // MyApp 返回 MaterialApp,透過這個來切換下一個 Widget
...
} catch (e, stack) {
...
} finally {
...
}
try {
// 一開始傳入 _child 是 null
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
...
}
...
}
}
abstract class Element extends DiagnosticableTree implements BuildContext {
void rebuild() {
assert(_lifecycleState != _ElementLifecycle.initial);
if (_lifecycleState != _ElementLifecycle.active || !_dirty)
return;
...
performRebuild();
...
}
@protected
void performRebuild();
}
從這裡可以看出,Flutter tree 的遍歷以深度為優先,在設計模式看來又是 裝飾模式
● Element 的設計模式 (裝飾 & 責任鏈)
A. 裝飾模式:ComponentElement 持有 Element,在透過 ComponentElement#build 來切換下一個子 Widget
B. 責任鏈:都是繼承 Element,並且要實 做 performRebuild 方法
更多的 Flutter/Dart 語言相關文章
了解 Flutter 如何在跨平台開發中佔據重要地位,掌握快速上手的技巧與項目建置流程,開啟你的跨平台開發之旅!
探索跨平台與 Flutter 技術的未來:從認識到 Flutter 專案建置 | 3 種跨平台
Dart 語言基礎
● 探討 Dart 語言:宣告、數據類型、操作符 | 從基礎到應用指南
快速掌握 Dart 語言的核心概念,包括變數宣告、數據類型及操作符,為 Flutter 開發奠定扎實基礎。
● Dart 函數與方法、異常處理、引用庫 | Java 比較
深入了解 Dart 的函數與異常處理特性,並與 Java 的處理方式進行比較,幫助你跨語言切換更加順暢。
● 深入解析 Dart 語言:命名慣例、類特性、建構函數與抽象特性
學習 Dart 類的設計邏輯及命名慣例,深入探索抽象類與 Mixin 的強大應用場景。
● 深入探索 Dart 的併發與異步處理:從 Isolate 到 Event Loop 的全面指南 | Future、Stream
徹底搞懂 Dart 的併發與異步處理,掌握 Isolate 與 Event Loop 的運行機制,助你提升應用效能!
深入 Flutter 框架
● 深入解析 Flutter Navigator:常見錯誤、解決方法與路由跳轉技巧、動畫
從常見問題到自定義解決方案,學會如何利用 Navigator 實現路由跳轉與流暢動畫效果。
● 深入理解 Flutter 中的數據共享:從普遍方案到 InheritedWidget | 3 種方案
探討數據共享的最佳實踐,了解 InheritedWidget 等三種主要方案,幫助你優化應用結構。
● 深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南
拆解 Flutter 的內部結構,全面了解 Widget、Element 和 RenderObject 之間的關係,提升你的 Flutter 開發技能!