前言
本篇記錄了 Flutter UI Framework 最基本的結構來認識它的渲染機制還有一些基本 Widget 的理解,以及 Flutter 所採用的 UI as Code 的開發方式是如何進行的。系列文章主要是 Stephen Grider 教學的筆記心得。
基本概念
- 在Flutter裡面, 幾乎所有東西都是小部件, 包括 alignment, padding
- Stateless widgets 是不可變的, 也就是說所有的屬性都不能改變 - 所有的數值都是final
Stateful widgets 維護在 widget 的生命週期中可能會改變的狀態(使用者交互, 更新資料等等)
實作一個
Stateful widget
需要至少兩個 class :- SatefulWidget class 創造一個
- State class 的實體
SatefulWidget class 本身是不可變的(和 Stateles s一樣), 但是 State class 可以在小部件的生命週期中持續存在
widget 的 state 會被緩衝(不被重新創建), 當 widget 被重新渲染會創建新的 widget 但他仍然使用被緩衝 state 的資料
Stateflul Widget v.s. Stateless Widget
Stateless Widget
雖說裡面的屬性和狀態不能被改變, 但資料可以在外面被改變傳進來, 來重新渲染該widgetStateful Widget
就更好理解- 可以透過外面傳進來的資料去改變
- 可以透過本地的 state 來改變
debugging UI
檢查整個app UI的介面 :
添加依賴
import ‘package:flutter/rendering.dart’;
在
main
裡面呼叫UI debuggerdebugPaintSizeEnabled = true;
其他debugger
1 | // 查看text的基準線(少用) |
ListView
定義 : 最常被用來滑動的 widget. 他展示了她一個接一個的子 widget 在滑動的方向上 (如果是橫向展示子 widget 被要求填滿 ListView)
以下有三種不同的選項來建構ListView :
ListView doc
Flutter 系統簡介
概述
Flutter 系統簡介
- Flutter 框架的本身是由許多層的抽象組成堆疊的
可以大概簡化成
- Material Cupertino & widget 是最上層也最常接觸到的一層
- e.g. 像是
Scaffold
&FloatingActionButton
… 是屬於Material
庫 ;Column
&GestureDetector
… 是屬於Widgets
庫
- e.g. 像是
- rendering layer 主要負責簡化外觀渲染的工作
- dart:ui 就是基本負責與更底層的
Flutter engine
溝通
- Material Cupertino & widget 是最上層也最常接觸到的一層
小結
越上層越容易控制使用,但越下層可以有更多複雜度的精細掌握
dart:ui
功能
該 lib
提供最底層的服務給 Flutter
框架用來引導整個應用程式,像是提供類別來驅動輸入 圖像文字 外觀 還有渲染系統
根據上述,有了 dart:ui 就可以開始開發了,因為它提供最基本的 (Canvas
, Paint
TextBox
) 等物件
- 缺點是難以管理
- 需要設計繪畫的所有座標
- 管理好每一張 frame
- 除了,小型專案適用
Rendering Library
The Flutter rendering tree
定義
- 一個在
render tree
裡的物件
- 一個在
功能
RenderObject
hierarchy 是被Flutter
的Widgets lib
來使用的,用來實現其佈局還有後端的渲染
根據上述,該層完成所有 Flutter 框架中負擔最重的部分(包含所有計算繪畫追蹤 frame 等部分)
RenderObject
想像成車子的引擎,他們負責實際渲染到 app 畫面的工作The tree is composed out of RenderObjects
實例化一個
RenderObject
是非常昂貴且耗時的,所以會盡量的cache
以避免資源的消耗 (所以 flutter 為何如此高效)
Widgets
Widgets 和 RenderObject 的關係
這大概是我們最可以輕易使用的 UI component
,每一個可以在 widget lib
裡面找到的 widget
都會有一個相對應的 RenderObject
在 rendering lib
裏來負責最終的渲染
所有的 Widgets
可以分成這三類
Layout e.g.
column
row
Painting e.g.
Text
image
Hit-Testing e.g.
GestureDetector
用來辨識所有的觸控
所有複雜的 widget
都是由以上這三種類別 wrap
而成
e.g. 製作一個
Button
可以由GestureDetector
再 包裝 wrap 一個Container
而成這種方式在 OO 叫做 composition 而不是 inheritance
Material & Cupertino library
Widgets lib all the time ?
其實除了自己組合所有複雜的 widget
也可以使用已經實作好的 Android & iOS 常用的設計元件
建構 Flutter UI 的詳細流程 (Widget, Element, RenderObject)
以此程式為例
由外往裡包的 widget 分別是
SimpleApp
=>SimpleContainer
=>SimpleText
整個 building (呼叫 runApp()
) 的過程大概可以分成三個步驟,每一個步驟都有自己的 tree
- Flutter 會建立一個包含這三個 widget 的
widget tree
- Flutter 會迭代整個
widget tree
並且創建第二個樹 -element tree
。在迭代的過程中,每一個widget
都會呼叫createElement()
來實例化相對應得Element
物件,並且將它塞到element tree
裡面 - 以此類推,第三棵樹 -
RenderTree
,透過Element
呼叫createRenderObject()
產生RenderObject
並且塞到RenderTree
圖示
Widget? Element? RenderObject?
每一個 Element
都會有一個 reference 參考到與其對應的 Widget
和 RenderObject
為什麼需要使用 Element 的前情提要:
RenderObject
包含了渲染與之相對應 Widget
的所有邏輯,因此實例化是相當昂貴的!所以盡可能地將它保留在記憶體中不要回收持續地利用
Elements
特性
- 就像一個代理人一樣連接兩端(reference),在不可變的 Widget tree 和可變的 RenderObject tree 之間 (immutable & mutable)
功能
- 用來比較同個位置下的
Widget
和RenderObject
為什麼要三棵樹不是一棵樹?
效能!!!
當每一次 widget tree
有改變的時候 (可能是某層換不同型態的 widget 或只是 widget 設定的更新), Flutter 用 element tree
來比較新建立的 Widget tree
和已經存在建立的 RenderObject tree
- 當該層的Widget和之前的 類別 type 是一樣的,就不需要重新建立相對應昂貴的
RenderObject
,而是只需要更新widget
有變動的設定給現有的RenderObject
就行了 - 反之,該層的
Widget
和之前的類別是不一樣的,就把舊的widget
,element
,renderObject
刪掉 (包括其子樹),然後重新創建相對應的elemnt
和renderObject
以此類推,迭代這三棵樹裡的所有物件
Widget 是輕量的所以可以被輕易的建造和消除所以適合用來描述目前的狀態和設定,反之 RenderObject 是重量級的所以應審慎的創建和回收
The whole app acts like a huge RecyclerView
BuildContext 和 Element
作為參數傳遞在 build(BuildContext context)
裡面的 BuildContext
,事實上就是包裝 Element
的物件,因此每一個 widget
都有獨一無二的 BuildContext
Conclusion
我們可以得知 Flutter 如此高效是因為有這三棵樹,分別是
widget tree
用來代表要使用的 UI 物件是什麼,再加上記錄一些簡單的狀態設定element tree
為兩棵樹間的代理人儲存相對應的每一層widget
和renderObject
的引用,用來比較彼此的相同或不同rederobject tree
為真實渲染到螢幕上的龐大昂貴的物件