---
title: 'Flutter 三棵樹:深入 UI 框架'
disqus: kyleAlien
---
Flutter 三棵樹:深入 UI 框架
===
## Overview of Content
關於 Flutter 的渲染機制,主要是透過三棵樹來進行的,分別是 ^1^ 最常操控的 **Widget**、^2^ 包在中間銜接渲染的 **Element**、^3^ 真正進行渲染的 **RenderObject**
> 這三顆樹是 UI 架構的核心概念,它們共同協作來構建、描述和渲染用戶界面
```mermaid
graph LR
subgraph Flutter 渲染機制
Widget --> Element --> RenderObject
end
```
:::info
以下使用的 Flutter 版本為 `3.22.2`
:::
:::success
* 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
本篇文章對應的是 [**深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南**](https://devtechascendancy.com/flutter-widget-element-renderobject-guide/)
:::
[TOC]
## 元件關係:包裹、繼承、樹關係
首先我們先從我們開發 Flutter 時最常接觸到的 Widget(也就是元件)開始看它們之間的依賴關係,進發現我們平常所用的 Widget 的「包裹」、「繼承」與「樹」關係
### Widget 樹:包裹、樹關係
* 直接創建一個 Flutter 專案,它會有基本的 Demo 示例,從 MyApp 類開始分析 Widget 樹狀圖… (這裡我們省略一步一步分析,直接看最表面的 Widget 關係)
```dart=
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`)的結構,並且其中存在著「**多元樹** 🌲」的關係
關係如下圖所示
> ![](https://i.imgur.com/RJWxXvx.png)
### Widget 繼承關係:發現 Element
* 上面有使用到的組件有 `Scaffold`、`AppBar`、`Text`、`Center`、`Colume`、`Icon`... 等等的組件,現在我們來看這些組件的「**繼承關係**」,會發現它們 **最終都會歸到 Widget 這個抽象類中**
> ![](https://i.imgur.com/kwZ9lXN.png)
而 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 |
1. `createElement()`:此方法是 Widget 最核心的部分,負責將 Widget 與 Element 系統進行對接
:::info
這裡我們會發現 `createElement()` 方法,這是第一次窺見到 Element(三顆樹中其中一個)… 該方法會讓 Widget 與 Element 產生關係
:::
2. `== 與 hashCode`:這兩個方法用於比較和管理 Widget 的唯一性,特別是在有狀態管理和 Widget 重建過程中
3. `canUpdate()`:這個方法決定了當 Widget 樹發生變化時,是否應該替換現有的 Widget,還是只進行更新
> ![image](https://hackmd.io/_uploads/H1O59EpnC.png)
## 認識三顆樹:Widget、Element、RenderObject
在 Flutter 框架中,Widget 樹、Element 樹 和 RenderObject 樹 是 **UI 架構的核心概念**,它們共同協作來構建、描述和渲染用戶界面
### 認識 Widget 樹:連接 Element 實例
* **認識 Widget 樹**:
Widget 是 Flutter 中最基本的構建單元,描述了應用程序的外觀和結構,並且 **每一個 Widget 都是一個靜態的、不可變的配置**
* **Widget 是一個抽象類**,並且有一個一定要實現的抽象方法 **==createElement==,該函數會返回一個 Element 類的實例**(這就是三顆樹的其中一個)
> 這樣就可以知道 **++Widget 的另外一個重點功能是在創建 Element 物件++**
而我們在寫 Flutter 應用的時候其實是不常碰到 Element,而是透過創建各種 Widget 來建構畫面,而其實 Widget 是讓我們與 Element 做隔離的元件
```mermaid
graph LR
開發者 --> |使用| Widget
Widget -.-> |創建| Element
```
看看 Widget 的繼承關係,可以知道 **所有繼承 Widget 的類都需要實現 Element 類,所以 Element 與 Widget 是一個一個對應的**,像是…
1. StatefulWidget → `StatefulElement`
2. StatelessWidget → `StatelessElement`
3. RenderObjectWidget → `RenderObjectElement`
> ![](https://i.imgur.com/c23cmp6.png)
### 不同 Widget 的 Element 實作
* 再來看看 Element 與各種各自實作 Element 的繼承關係,這邊除了有常見的 `StatefulElement` & `StatelessElement`,還有**看見另外一個 `RenderObjectElement`** 類;接下來會做一些簡單的介紹…
> ![](https://i.imgur.com/3yeEbHc.png)
1. **StatefulElement 類**
* 使用場景: 用於管理與 StatefulWidget 關聯的 Element
* 主要功能:
* **當 StatefulWidget 重建時,StatefulElement 保持 State 不變**,**這樣即使 Widget 被重建**,**狀態仍然保留**(例如:計數器、表單中的輸入數據等)
* StatefulElement 與一個 State 物件相關聯,這個 **State 是可變的**,並且在 Widget 的生命週期中持續存在
:::info
也就是我們平常在更新 Widget 畫面時,會使用的 `setState` 方法,就是用來刷新 StatefulElement 的 State!
:::
* StatefulElement 通過調用 State 的 `build()` 方法來構建和更新其子樹
```mermaid
graph TD
StatefulWidget --> |關聯| StatefulElement
StatefulElement --> State
StatefulElement -->|build| Widget
State -->|setState| StatefulElement
Widget -->|rebuild| StatefulElement
```
2. **StatelessElement 類**
* 使用場景: 用於管理與 StatelessWidget 關聯的 Element
* 主要功能:
* **StatelessElement 不會保留任何狀態**,當它的父 Widget 樹更新時,只會調用 StatelessWidget 的 `build()` 方法來重建 UI
> 也就是 StatelessElement 不具有 State,所以重新建構時是重新建構 Element
* 由於 StatelessWidget 是不可變的,它們每次更新時都會丟棄舊的元素並使用新元素重新構建(也就是缺乏 State,所以不會保留任何狀態)
> StatelessElement 不關心狀態的變化,因為 StatelessWidget 只關注不可變的 UI 呈現
```mermaid
graph TD
StatelessWidget --> StatelessElement
StatelessElement -->|build| Widget
Widget -->|rebuild| StatelessElement
```
3. **RenderObjectElement 類**(跟佈局更相關的 Element)
* 使用場景: 用於管理與 RenderObjectWidget 關聯的 Element
* 主要功能:
* **RenderObjectElement 主要負責創建和管理 RenderObject,這是用於處理佈局和繪製的物件**
* 當 Widget 發生變化時,RenderObjectElement 決定如何更新或重建其對應的 RenderObject
:::success
* 它與 `StatefulElement` 和 `StatelessElement` 不同,**RenderObjectElement 的重點是「處理佈局、尺寸和繪製邏輯」**,而不是單純地重建子 Widget 樹(請注意這裡說的是邏輯… 至於繪製則是交給 `RenderObject` 類)
> 它通常會用於像 `Container`、`CustomPaint` 等需要處理具體佈局和繪製的 Widget
:::
```mermaid
graph TD
RenderObjectWidget --> RenderObjectElement
RenderObjectElement -->|createRenderObject| RenderObject
RenderObject -->|layout、paint| Screen
RenderObjectElement -->|update| RenderObject
```
### 認識 RenderObject 樹:Widget 與 RenderObject 關係
* 我們知道一個 Widget 通常是依賴著其他 Widget 建構 (初始專案的 MyApp 就是這樣一個 Widget),而現在我們就來找找 Flutter SDK 提供的原生 Widget 最終是如何實現的
> ![](https://i.imgur.com/yeXFdxE.png)
以 `Text`、`Column`、`Center`、`Icon`、`Image` Widget… 來做觀察 (注意顏色、線條,個代表了不同意義),**可以看到最終元件都會與於 RenderObject 產生關係**(大部分都會透過 Element 與 RenderObject 產生關係)
:::success
* **可以把 Widget 看成是一個 ++設計稿++**,將稿 (Widget) 完成後就不會再改變,這時就只需要放入對應的元件(`RenderObject`) 就可以生成不同的 UI 介面
**RenderObject 才是真正渲染畫面的物件**
:::
* **那 RenderObject 跟 RenderObjectElement 是同樣含義嗎?** **並不是~** 兩者主要負責的責任不同
1. **RenderObject 類**:負責繪製
RenderObject 是一個專門負責處理 `佈局`、`繪製`、`命中測試` 和 `邊界處理` 的物件,「**它是渲染層的核心**」,負責實際在螢幕上繪製和排列可視的內容
> RenderObject 決定 Widget 應該如何呈現在螢幕上,它處理像素繪製、邊界計算以及 Widget 的尺寸與位置
2. **RenderObjectElement 類**:負責連接
RenderObjectElement 是 Element 的一個具體子類,「**它負責將 Widget 和 RenderObject 連接起來**」…
> 並且它管理 Widget 和 RenderObject 之間的關係,並確保 RenderObject 正確地反映 Widget 的狀態
```mermaid
graph LR
RenderObject <--> |連接| RenderObjectElement <--> |連接| Widget
RenderObject -.-> 繪製
```
### 進一步認識 RenderObject
* 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 負責個別元件的渲染
> ![image](https://hackmd.io/_uploads/r1EuVra30.png)
* 再來看看 RenderObject 中幾個比較重要的方法(它的方法太多了,這邊我們只挑幾種介紹),**從這些方法就可以看出 ==RenderObject 可布局、繪畫、響應事件==**
| RenderObject 方法 | 說明 |
| -------- | -------- |
| `layout` | 就像是 Android's onLayout,用來 **對元件布局** |
| `paint` | 就像是 Android's onDraw,用來 **繪製元件** |
| `handleEvent` | 就像是 Android's dispatchTouchEvent ,用來**處理事件** |
看看 SDK 提供的 Widget 是否有重寫 layout、paint、handleEvent 方法,並且每個 Widget 的著重重點在哪裡
> ![](https://i.imgur.com/q2DpjoB.png)
1. 可見元件 TextWidget:藉由上圖,我們這邊直接看 TextWidget 對應的 RenderBox 的子類 `RenderParagraph`
我們可以看到 RenderParagraph#paint 方法在繪製時主要是透過 `TextPainter` 去繪製文字,而繪製是繪製在 Canvas 之上
```dart=
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);
}
}
```
2. 可見元件 ImageWidget:藉由上圖,我們這邊直接看 ImageWidget 對應的 RenderBox 的子類 `RenderImage`
我們可以看到 RenderImage#paint 方法在繪製時主要是透過 `paintImage` 方法去繪製圖片,繪製同樣是繪製在 Canvas 之上
```java=
// 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);
}
```
3. 布局元件 Column:這裡 paint 就不是重點,重點在 layout
```java=
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 相關的依賴上下文 |
```dart=
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 一般)
| 生命狀態 | 說明 |
| -------- | -------- |
| `initial` | 剛建構 Widget 時尚未連接上 View Tree |
| `active` | **透過 `mount` 方法連接到 View Tree**,並顯示在畫面上 |
| `inactive` | 可透過 deactivateChild 將某個下層元素從 View Tree 剔除,也在畫面上 |
| `defunct` | 當框架調用 unmount 時,會從樹中移除,並且再也不會回到 View Tree |
```dart=
enum _ElementLifecycle {
initial, // 初始化
active, // 激活
inactive, // 未激活
defunct, // 死亡
}
```
而生命週期的變動,是可以通過幾種方法的調用來達到… 舉例:像是通過調用 Element#`mount` 方法,就可以激活該元件到 active 狀態
> `mount` 之後會再詳細介紹
```java=
// 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 在掛載時的結構層級
```dart=
// framework.dart
void mount(Element? parent, Object? newSlot) {
...
_parent = parent;
print("_parent: ${parent.runtimeType}");
...
}
```
如下圖所見
由於這個 Tree 太長,我就不全部列出了,沒有太大的意義,**重點我們可以看到 ++Element 的根物件的類是 `RootElement`++**
> ![image](https://hackmd.io/_uploads/BJCwnT1pA.png)
:::success
* 或是你在 Debug 模式下把斷點設置在 MyApp#`build` 函數中,並在進入斷點時,查看 `_parent` 成員,一直點到最底層也可以看到跟物件為 `RootElement`
> ![image](https://hackmd.io/_uploads/SyYDpaJ6A.png)
:::
* 接著,我們要找尋 RootElement 的持有者(是誰創建的),可以透過以下方法
1. 先把斷點下在 RootElement 類的建構函數,在 Debug 模式運行 APP
> ![image](https://hackmd.io/_uploads/HkSiaTkpR.png)
2. 在進入斷點後,單步執行,就可以發現 **RootElement 是由 ==RootWidget==** 類創建
> ![image](https://hackmd.io/_uploads/HJZlCpJa0.png)
### 找 RanderObject 根類
* 接續上個小節,在 Debug 模式下,我們透過查看 Element 的根結點類(`RootElement`)的 `_renderObject` 成員
就可以發現負責渲染畫面的 **渲染樹(`RanderObject`)的根節點就是 `_ReusableRenderView`,而開啟後會發現 `_ReusableRenderView` 就是繼承於 ==RenderView==** 類
> ![image](https://hackmd.io/_uploads/S1P1lRy6C.png)
:::info
而 `_ReusableRenderView` 被創建的時機則是在 `runApp` 初始化的過程中,下面小節會再介紹
:::
## runApp 初始化、建構
我們來分析 ++`runApp()`++ 這個函數:從這個函數我們往下看,就可以看到三棵樹的初始化過程做了什麼事情,並且清楚的知道 Flutter App 是如何初始化應用並與 UI 框架產生關聯的
```dart=
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` 有以下功能
1. GestureBinding 手勢系統
2. SchedulerBinding 調度系統
3. ServicesBinding 綁定平台消息通道
4. PaintingBinding 繪製
5. WidgetsBinding 連接組件 & 引擎
```dart=
// 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 方法,而這個方法會被其許多混合繼承類複寫
```dart=
// foundation/binding.dart
abstract class BindingBase {
BindingBase() {
...
initInstances();
...
}
@protected
@mustCallSuper
void initInstances() {
assert(!_debugInitialized); // 保證該函數只會執行一次
assert(() {
_debugInitialized = true;
return true;
}());
}
}
```
> ![image](https://hackmd.io/_uploads/ryIQlCypC.png)
而這些方法執行的順序就較為不同 (簡單來說 **由最後一個覆寫的物件開始執行**),但是!它內部有是使用到 `super` 關鍵字,由於 `super` 與 `with` 的配合,最終會導致 `initInstances` 的順序仍是由 `GestureBinding` 到 `WidgetsBinding`
1. 先執行 WidgetsBinding 類的 initInstances 方法
```dart=
// widgets/binding.dart
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances(); // 呼叫到 RendererBinding
_instance = this;
...
_buildOwner = BuildOwner();
}
}
```
2. 再接續會執行 RendererBinding 類的 initInstances 方法
```dart=
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances(); // 呼叫到 SemanticsBinding
...
}
}
```
3. 再接續會執行 SemanticsBinding 類的 initInstances 方法
```dart=
mixin SemanticsBinding on BindingBase {
@override
void initInstances() {
super.initInstances(); // 呼叫到 PaintingBinding
...
}
}
```
4. 再接續會執行 PaintingBinding 類的 initInstances 方法
```dart=
mixin PaintingBinding on BindingBase, ServicesBinding {
@override
void initInstances() {
super.initInstances(); // 呼叫到 SchedulerBinding
...
}
}
```
5. 再接續會執行 SchedulerBinding 類的 initInstances 方法
```dart=
mixin SchedulerBinding on BindingBase {
@override
void initInstances() {
super.initInstances(); // 呼叫到 SemanticsBinding
...
}
}
```
6. 再接續會執行 SemanticsBinding 類的 initInstances 方法
```dart=
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
@override
void initInstances() {
super.initInstances(); // 最終回到 BindingBase 的 initInstances 方法
// 執行完 BindingBase 的 initInstances 方法後又回到到這裡繼續執行
...
// 執行完往 SchedulerBinding 回推
}
}
```
:::warning
* **`with` 與 `super` 執行順序**
如同前面的說,是從最後一個覆寫得類開始執行,**但在這裡透過 super 不斷執行父類方法**(像是 `WidgetsBinding` 不斷執行 super 後會接續到 `GestureBinding`),最終全部執行完後又是從 `GestureBinding` 執行到 `WidgetsBinding`
:::
> ![](https://i.imgur.com/M4clzrD.png)
```mermaid
graph TB
WidgetsBinding --> |1. super| RendererBinding --> |2. super| SemanticsBinding --> |3. super| PaintingBinding --> |4. super| ServicesBinding --> |5. super| SchedulerBinding --> |6. super| BindingBase
BindingBase -.-> |7.| SchedulerBinding -.-> |8.| ServicesBinding -.-> |9.| PaintingBinding -.-> |10.| SemanticsBinding -.-> |11.| RendererBinding -.-> |12.| WidgetsBinding
```
### 連接根 Widget:創建 RootElement、RenderView
```dart=
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`
```dart=
// 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`
```java=
// rendering/binding.dart
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
late final RenderView renderView = _ReusableRenderView(
view: platformDispatcher.implicitView!,
);
}
```
:::warning
但這個做法可能在新版本被遺棄,改由 renderViews 管理(因為 RendererBinding 或許需要管理多個 RenderView)
:::
### 根 Widget 調用 mount:掛載 RootWidget
* 我們前面說在初始化時會創建 `RootWidget` 物件(根 Widget),接著我們來看 Widget 何時被掛載上去的,我們從 `attachToBuildOwner` 函數開始…
```dart=
// 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();
}
}
```
1. 在 `attachToBuildOwner` 函數中會調用 RootWidget#`attach` 方法
2. 而在 RootWidget#`attach` 方法中又會發現它調用了 RootElement#`mount` 方法,剛方法會把 RootElement 掛載到 Flutter Widget 樹中進行初始化
```dart=
// 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`)
```dart=
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!;
}
```
並且接下來我們會分析幾個重點
1. RootElement#`mount` 方法
2. Element#`mount` 方法
### Element 的 mount 方法:初始化生命週期
* 我們來看看最原始的 Element#`mount` 方法,所有繼承於 Element 類的元素都需要呼叫這個方法… 從以下程式中,可以發現 `mount` 主要就是決定 Widget parent、深度、擁有者… 等等資訊
```dart=
// 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()` 方法
```dart=
// 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
}
...
}
```
1. `_rebuild()` 方法中主要做的事情就是呼叫 Element#`updateChild(...)` 方法,該方法主要是返回子 Widget 元素
```dart=
// widgets/binding.dart
Element? _child;
void _rebuild() {
try {
_child = updateChild(_child, (widget as RootWidget).child, /* slot */ null);
} catch (exception, stack) {
... 省略部分
}
}
```
2. **`updateChild` 函數**:這裡我們當成是初次加載,所以在初次呼叫時 `_child` 成員為 null,透過一些省略… 在帶入 `updateChild` 函數後我們可以簡化為以下程式來看
我們可以看到它主要是再呼叫了 `inflateWidget` 方法
```dart=
// 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;
}
```
3. **`inflateWidget` 函數**:該函數會先判斷 Key 是否是 GlobalKey,如果是 GlobalKey 則呼叫 `updateChild` 更新… 否則就呼叫 `createElement` 加載子類的 Element
```dart=
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();
}
}
}
```
:::info
這裡可以很明顯地看出一個遞迴加載 Widget 的過程
```mermaid
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 總共有四種狀況,如下表所示
| | `newWidget == null` | `newWidget != null` |
| -------- | -------- | -------- |
| child == null | ^1^ 返回 null | ^3^ 用 `inflateWidget` 返回新元素 |
| child != null | ^2^ 移除原來的 Child,返回 null | ^4^ 返回 Child or inflateWidget |
```dart=
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 父類去找
> StatelessElement 對應 StatelessWidget
```java=
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);
}
```
> ![](https://i.imgur.com/2jMIhRO.png)
* 當 inflateWidget 調用 `createElement` 後會創建 `StatelessElement` 物件
其反應的順序簡單來說就是 `ComponentElement#mount` -> `_firstBuild` -> `rebuild` -> `performRebuild`
```java=
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 的==遍歷以深度為優先==,在設計模式看來又是 ==裝飾模式==**
> ![](https://i.imgur.com/njzalAY.png)
* **Element 的設計模式 (裝飾 & 責任鏈)**
1. **裝飾模式**:ComponentElement 持有 Element,在透過 ComponentElement#build 來切換下一個子 Widget
2. **責任鏈**:都是繼承 Element,並且要實 做 performRebuild 方法
> ![](https://i.imgur.com/abep95W.png)
### 掛載多元素:Column Widget
* 當我們使用 Column 組件時 inflateWidget 被調用後,加載的 Element 會是 `MultiChildRenderObjectElement`
```java=
// framework
abstract class Element extends DiagnosticableTree implements BuildContext {
Element inflateWidget(Widget newWidget, Object? newSlot) {
assert(newWidget != null);
final Key? key = newWidget.key;
...
// 若沒有全域的 Key 則呼叫 createElement
final Element newChild = newWidget.createElement(); // 重點!!!
...
newChild.mount(this, newSlot); // 調用 Child 自己的 Element,並且 將自己作為 parent 傳入 (mount 的第一個參數 this)
assert(newChild._lifecycleState == _ElementLifecycle.active);
return newChild;
}
}
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
MultiChildRenderObjectWidget({ Key? key, this.children = const <Widget>[] })
: assert(children != null),
super(key: key) {
...
}
final List<Widget> children;
// inflateWidget 就是回傳 MultiChildRenderObjectElement
@override
MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}
class MultiChildRenderObjectElement extends RenderObjectElement {
@override
Element inflateWidget(Widget newWidget, Object? newSlot) {
final Element newChild = super.inflateWidget(newWidget, newSlot);
assert(_debugCheckHasAssociatedRenderObject(newChild));
return newChild;
}
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot); // 找到 _parent,以 MyApp 來說就是 Center
...
}
}
```
Colume 調用順序如下圖
> ![](https://i.imgur.com/S5dpeYP.png)
* 調用 mount 方法時,會先調用 super.mount,就是要找到 `_parent`,以 MyApp 來說就是 Center
> ![](https://i.imgur.com/54rx5Ik.png)
* 只有 RenderObjectElement 才會有對應的渲染節點,而 **MultiChildRenderObjectElement 會呼叫 inflateWidget 將子類將掛載到 View Tree 上**
```java=
// framework
abstract class RenderObjectElement extends Element {
RenderObject? _renderObject;
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
...
_renderObject = widget.createRenderObject(this); // 創建渲染對象
...
attachRenderObject(newSlot);
_dirty = false;
}
}
class MultiChildRenderObjectElement extends RenderObjectElement {
@override
Element inflateWidget(Widget newWidget, Object? newSlot) {
final Element newChild = super.inflateWidget(newWidget, newSlot);
... 斷言
return newChild;
}
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false);
Element? previousChild;
for (int i = 0; i < children.length; i += 1) {
// inflateWidget 最終會觸發到 Element#mount (掛載好自己)
final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild));
children[i] = newChild;
previousChild = newChild;
}
_children = children;
}
}
```
## Appendix & FAQ
:::info
:::
###### tags: `Flutter`