1 Widget 簡介
在Flutter中丈秩,一切皆是Widget(組件),Widget的功能是“描述一個UI元素的配置數(shù)據(jù)”淳衙,它就是說蘑秽,Widget其實并不是表示最終繪制在設備屏幕上的顯示元素,而它只是描述顯示元素的一個配置數(shù)據(jù)箫攀。
實際上肠牲,F(xiàn)lutter中真正代表屏幕上顯示元素的類是 Element,也就是說Widget 只是描述 Element 的配置數(shù)據(jù)靴跛。并且一個 Widget 可以對應多個 Element缀雳,因為同一個 Widget 對象可以被添加到 UI樹的不同部分,而真正渲染時梢睛,UI樹的每一個 Element 節(jié)點都會對應一個 Widget 對象肥印。
其中組件又包括無狀態(tài)組件和有狀態(tài)組件。
無狀態(tài)組件(StatelessWidget)
無狀態(tài)組件扬绪,可以理解為將外部傳入的數(shù)據(jù)轉化為界面展示的內容竖独,只會渲染一次。有狀態(tài)組件(StatefulWidget)
有狀態(tài)組件挤牛,是定義交互邏輯和業(yè)務數(shù)據(jù)莹痢,可以理解為具有動態(tài)可交互的內容界面,會根據(jù)數(shù)據(jù)的變化進行多次渲染墓赴。
StatelessWidget 和 StatefulWidget 都是直接繼承自 Widget 類竞膳,而這兩個類也正是 Flutter 中非常重要的兩個抽象類,它們引入了兩種 Widget 模型诫硕。
2 兩種Widget模型
2.1 StatelessWidget
@override
StatelessElement createElement() => new StatelessElement(this);
StatelessWidget相對比較簡單坦辟,它繼承自Widget類,重寫了createElement()方法章办。
StatelessElement 間接繼承自Element類锉走,與StatelessWidget相對應(作為其配置數(shù)據(jù))。
StatelessWidget用于不需要維護狀態(tài)的場景藕届,并且只會被渲染一次挪蹭,它通常在build方法中通過嵌套其它Widget來構建UI,在構建過程中會遞歸的構建其嵌套的Widget休偶。
2.2 StatefulWidget
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => new StatefulElement(this);
@protected
State createState();
}
和StatelessWidget一樣梁厉,StatefulWidget也是繼承自Widget類,并重寫了createElement()方法踏兜,不同的是返回的Element 對象并不相同词顾;另外StatefulWidget類中添加了一個新的接口createState()八秃。
StatefulElement 間接繼承自Element類,與StatefulWidget相對應(作為其配置數(shù)據(jù))肉盹。StatefulElement中可能會多次調用createState()來創(chuàng)建狀態(tài)(State)對象昔驱。
createState() 用于創(chuàng)建和StatefulWidget相關的狀態(tài),它在StatefulWidget的生命周期中可能會被多次調用垮媒。例如舍悯,當一個StatefulWidget同時插入到widget樹的多個位置時航棱,F(xiàn)lutter framework就會調用該方法為每一個位置生成一個獨立的State實例睡雇,其實,本質上就是一個StatefulElement對應一個State實例饮醇。
2.2.1 State
一個StatefulWidget類會對應一個State類它抱,State表示與其對應的StatefulWidget要維護的狀態(tài),State中的保存的狀態(tài)信息可以:
- 在widget 構建時可以被同步讀取朴艰。
- 在widget生命周期中可以被改變观蓄,當State被改變時,可以手動調用其setState()方法通知Flutter framework狀態(tài)發(fā)生改變祠墅,F(xiàn)lutter framework在收到消息后侮穿,會重新調用其build方法重新構建widget樹,從而達到更新UI的目的毁嗦。
State中有兩個常用屬性:
- widget亲茅,它表示與該State實例關聯(lián)的widget實例,由Flutter framework動態(tài)設置狗准。注意克锣,這種關聯(lián)并非永久的,因為在應用生命周期中腔长,UI樹上的某一個節(jié)點的widget實例在重新構建時可能會變化袭祟,但State實例只會在第一次插入到樹中時被創(chuàng)建,當在重新構建時捞附,如果widget被修改了巾乳,F(xiàn)lutter framework會動態(tài)設置State.widget為新的widget實例。
- context鸟召,StatefulWidget對應的BuildContext胆绊,作用同StatelessWidget的BuildContext,表示當前widget在widget樹中的上下文药版,每一個widget都會對應一個context對象(因為每一個widget都是widget樹上的一個節(jié)點)辑舷。
3 生命周期
3.1 組件生命周期
Flutter 中說的生命周期,是獨指有狀態(tài)組件的生命周期槽片,對于無狀態(tài)組件生命周期只有一次 build 這個過程何缓,也只會渲染一次肢础。有狀態(tài)組件的生命周期如下圖:
Flutter 中的生命周期,包含以下幾個階段:
createState 碌廓,該函數(shù)為 StatefulWidget 中創(chuàng)建 State 的方法传轰,當 StatefulWidget 被調用時會立即執(zhí)行 createState 。
initState 谷婆,該函數(shù)為 State 初始化調用慨蛙,因此可以在此期間執(zhí)行 State 各變量的初始賦值,同時也可以在此期間與服務端交互纪挎,獲取服務端數(shù)據(jù)后調用 setState 來設置 State期贫。
didChangeDependencies 遏片,當State對象的依賴發(fā)生變化時會被調用涮俄;例如:在之前build() 中包含了一個InheritedWidget,然后在之后的build() 中InheritedWidget發(fā)生了變化前域,那么此時InheritedWidget的子widget的didChangeDependencies()回調都會被調用烤蜕。典型的場景是當系統(tǒng)語言Locale或應用主題改變時封孙,F(xiàn)lutter framework會通知widget調用此回調。
build 讽营,主要是返回需要渲染的 Widget 虎忌,由于 build 會被調用多次,因此在該函數(shù)中只能做返回 Widget 相關邏輯橱鹏,避免因為執(zhí)行多次導致狀態(tài)異常膜蠢。在 build 之后還有個回調
addPostFrameCallback
,在當前幀繪制完成后會回調蚀瘸,注冊之后不能被解注冊并且只會回調一次狡蝶;addPostFrameCallback
是 SchedulerBinding 的方法;由于mixin WidgetsBinding on SchedulerBinding
贮勃,所以添加這個回調有兩種方式:SchedulerBinding.instance.addPostFrameCallback((_) => {});
或者WidgetsBinding.instance.addPostFrameCallback((_) => {});
reassemble贪惹, 在 debug 模式下,每次熱重載都會調用該函數(shù)寂嘉,因此在 debug 階段可以在此期間增加一些 debug 代碼奏瞬,來檢查代碼問題。
didUpdateWidget 泉孩,在widget重新構建時硼端,F(xiàn)lutter framework會調用Widget.canUpdate來檢測Widget樹中同一位置的新舊節(jié)點,然后決定是否需要更新寓搬,如果Widget.canUpdate返回true則會調用此回調珍昨。正如之前所述,Widget.canUpdate會在新舊widget的key和runtimeType同時相等時會返回true,也就是說在在新舊widget的key和runtimeType同時相等時didUpdateWidget()就會被調用镣典。父組件發(fā)生 build 的情況下兔毙,子組件該方法才會被調用,其次該方法調用之后一定會再調用本組件中的 build 方法兄春。
deactivate 澎剥,在組件被移除節(jié)點后會被調用,如果該組件被移除節(jié)點赶舆,然后未被插入到其他節(jié)點時哑姚,則會繼續(xù)調用 dispose 永久移除。
dispose 芜茵,永久移除組件叙量,并釋放組件資源。
Flutter 生命周期的整個過程可以分為四個階段
- 初始化階段:createState 和 initState
- 組件創(chuàng)建階段:didChangeDependencies 和 build
- 觸發(fā)組件 build:didChangeDependencies夕晓、setState 或者didUpdateWidget 都會引發(fā)的組件重新 build
- 組件銷毀階段:deactivate 和 dispose
3.2 組件首次加載過程
我們通過代碼來看下組件首次加載執(zhí)行的生命周期過程
在 lib 中創(chuàng)建 test_stateful_widget.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class TestStatefulWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
print('create state');
return TestState();
}
}
class TestState extends State<TestStatefulWidget> {
/// 定義 state [count] 計算器
int count = 1;
/// 定義 state [name] 為當前描述字符串
String name = 'test';
@override
void initState() {
print('init state');
super.initState();
}
@override
void didChangeDependencies() {
print('did change dependencies');
super.didChangeDependencies();
}
@override
void didUpdateWidget(TestStatefulWidget oldWidget) {
count++;
print('did update widget');
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print('deactivate');
super.deactivate();
}
@override
void dispose() {
print('dispose');
super.dispose();
}
@override
void reassemble() {
print('reassemble');
super.reassemble();
}
void changeName() {
setState(() {
print('set state');
this.name = 'Flutter';
});
}
@override
Widget build(BuildContext context) {
print('build');
return Column(
children: <Widget>[
FlatButton(
child: Text('$name $count'), onPressed: () => this.changeName())
],
);
}
}
在 main.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: 'Test',
theme: ThemeData(
primaryColor: Colors.amberAccent
),
home: Scaffold(
appBar: AppBar(
title: Text('生命周期測試'),
),
body: Center(
child: TestStatefulWidget(),
),
),
);
}
}
我們打開手機模擬器宛乃,然后運行該 App ,在輸出控制臺可以看到下面的運行打印日志信息:
I/flutter ( 7729): create state
I/flutter ( 7729): init state
I/flutter ( 7729): did change dependencies
I/flutter ( 7729): build
當我們點擊 Android Studio 中的 Hot Reload 按鈕(黃色小閃電 ??)時控制臺輸出信息如下:
I/flutter ( 7729): reassemble
I/flutter ( 7729): did update widget
I/flutter ( 7729): build
3.3 觸發(fā)組件再次 build
觸發(fā)組件再次 build 有三種方式
- setState
- didChangeDependencies
- didUpdateWidget
setState的場景開發(fā)中你那個經常遇到蒸辆,在數(shù)據(jù)狀態(tài)變化時觸發(fā)組件 build,在上面的代碼運行后的界面中點擊 test 文本按鈕析既,然后觀察控制臺輸出如下:
I/flutter ( 7729): set state
I/flutter ( 7729): build
didChangeDependencies場景:一般情況下我們會將一些比較基礎的數(shù)據(jù)放到全局變量中躬贡,例如主題顏色、地區(qū)語言或者其他通用變量等眼坏。如果這些全局 state 發(fā)生狀態(tài)變化則會觸發(fā)該函數(shù)拂玻,而該函數(shù)之后就會觸發(fā) build 操作。
接下來看下didUpdateWidget 觸發(fā) build 的場景:
創(chuàng)建一個組件 SubStatefulWidget 繼承 TestStatefulWidget
class SubStatefulWidget extends TestStatefulWidget {
@override
State<StatefulWidget> createState() {
print('sub create state');
return SubState();
}
}
class SubState extends State<SubStatefulWidget> {
@override
void initState() {
print('sub init state');
super.initState();
}
@override
void didChangeDependencies() {
print('sub did change dependencies');
super.didChangeDependencies();
}
@override
void didUpdateWidget(TestStatefulWidget oldWidget) {
print('sub did update widget');
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print('sub deactivate');
super.deactivate();
}
@override
void dispose() {
print('sub dispose');
super.dispose();
}
@override
void reassemble() {
print('sub reassemble');
super.reassemble();
}
void changeName() {
setState(() {
print('sub set state');
});
}
@override
Widget build(BuildContext context) {
print('sub build');
return Text('SubStatefulWidget');
}
}
接著在 TestStatefulWidget 加載該組件
@override
Widget build(BuildContext context) {
print('build');
return Column(
children: <Widget>[
FlatButton(
child: Text('$name $count'), onPressed: () => this.changeName()),
SubStatefulWidget()
],
);
}
重新加載 APP宰译,觀看控制臺輸出如下:
I/flutter ( 8464): create state
I/flutter ( 8464): init state
I/flutter ( 8464): did change dependencies
I/flutter ( 8464): build
I/flutter ( 8464): sub create state
I/flutter ( 8464): sub init state
I/flutter ( 8464): sub did change dependencies
I/flutter ( 8464): sub build
上面日志先后打印了 TestStatefulWidget 與 SubStatefulWidget 四個狀態(tài)函數(shù) createState檐蚜、initState、didChangeDependencies 和 build沿侈。當我們再次點擊 test 文本按鈕闯第,觀察控制臺輸出如下:
I/flutter ( 9425): set state
I/flutter ( 9425): build
I/flutter ( 9425): sub did update widget
I/flutter ( 9425): sub build
通過上面打印的日志我們可知,父組件調用setState不僅會觸發(fā)自己 build缀拭,還會引發(fā)子組件重新 build 咳短,雖然子組件沒有任何改動。
3.4 觸發(fā)組件銷毀
在3.3的代碼的基礎上蛛淋,我們直接在 TestStatefulWidget 組件中注釋子組件 SubStatefulWidget 的調用
@override
Widget build(BuildContext context) {
print('build');
return Column(
children: <Widget>[
FlatButton(
child: Text('$name $count'), onPressed: () => this.changeName()),
// SubStatefulWidget()
],
);
}
然后點擊 Android Studio 上的 Hot Reload 按鈕觀察控制臺輸出如下信息:
I/flutter ( 9425): reassemble
I/flutter ( 9425): sub reassemble
I/flutter ( 9425): did update widget
I/flutter ( 9425): build
I/flutter ( 9425): sub deactivate
I/flutter ( 9425): sub dispose
可以看到 SubStatefulWidget 被銷毀
4 App 生命周期監(jiān)聽
在 Flutter 中咙好,可以利用 WidgetsBindingObserver 類來監(jiān)聽App 生命周期
abstract class WidgetsBindingObserver {
// 頁面 pop
Future<bool> didPopRoute() => Future<bool>.value(false);
// 頁面 push
Future<bool> didPushRoute(String route) => Future<bool>.value(false);
// 系統(tǒng)窗口相關改變回調,如旋轉
void didChangeMetrics() { }
// 文本縮放系數(shù)變化
void didChangeTextScaleFactor() { }
// 系統(tǒng)亮度變化
void didChangePlatformBrightness() { }
// 本地化語言變化
void didChangeLocales(List<Locale> locale) { }
//App 生命周期變化
void didChangeAppLifecycleState(AppLifecycleState state) { }
// 內存警告回調
void didHaveMemoryPressure() { }
//Accessibility 相關特性回調
void didChangeAccessibilityFeatures() {}
}
其中didChangeAppLifecycleState(AppLifecycleState state)
就是用來監(jiān)聽App生命周期褐荷。在 didChangeAppLifecycleState 回調函數(shù)中勾效,有一個參數(shù)類型為 AppLifecycleState 的枚舉類,這個枚舉類是 Flutter 對 App 生命周期狀態(tài)的封裝。它的常用狀態(tài)包括 resumed层宫、inactive绘迁、paused 這三個。
- resumed:可見的卒密,并能響應用戶的輸入缀台。
- inactive:處在不活動狀態(tài),無法處理用戶響應哮奇。
- paused:不可見并不能響應用戶的輸入膛腐,但是在后臺繼續(xù)活動中。
當切換前鼎俘、后臺時哲身,App 狀態(tài)如下:
- 從后臺切入前臺: AppLifecycleState.paused -> AppLifecycleState.inactive -> AppLifecycleState.resumed
- 從前臺退回后臺:AppLifecycleState.resumed -> AppLifecycleState.inactive -> AppLifecycleState.paused
5 幀繪制回調
5.1 addPostFrameCallback
單次 Frame 繪制回調,會在當前 Frame 繪制完成后進行回調贸伐,并且只會回調一次勘天,如果要再次監(jiān)聽則需要再設置一次。
WidgetsBinding.instance.addPostFrameCallback((_){
print(" 單次 Frame 繪制回調 ");
});
5.2 addPersistentFrameCallback
實時 Frame 繪制回調捉邢,會在每次繪制 Frame 結束后進行回調脯丝,可以用做 FPS 監(jiān)測。
WidgetsBinding.instance.addPersistentFrameCallback((_){
print(" 實時 Frame 繪制回調");
});
6 總結
- Flutter 中 Widget 分為兩種:StatelessWidget 和 StatefulWidget
- Flutter 生命周期的整個過程可以分為四個階段:初始化伏伐、組件創(chuàng)建宠进、觸發(fā)組件 build、組件銷毀
- StatelessWidget 用于不需要維護狀態(tài)的場景藐翎,只會 build 一次
- StatefulWidget 會被多次觸發(fā) build 函數(shù)材蹬,觸發(fā)函數(shù)是setState、didChangeDependencies吝镣、didUpdateWidget
- 父組件調用setState不僅會觸發(fā)自己 build堤器,還會引發(fā)子組件重新 build ,雖然子組件沒有任何改動
- 可以利用 WidgetsBindingObserver 類來監(jiān)聽App 生命周期
- 幀繪制回調有單次 Frame 繪制回調(addPostFrameCallback)和實時 Frame 繪制回調(addPersistentFrameCallback)