widget 簡介
Stateless vs Stateful
Stateless Widget
繼承自Widget, 重寫createElement() 方法:
@override
StatelessElement createElement() => new StatelessElement(this);
複製程式碼
StatelessElement
間接繼承自Element
類,與StatelessWidget相對應(作為其配置資料).
StatelessWidget用於不需要維護狀態的場景,它通常在build
方法中通過巢狀其它Widget來構建UI,在構建過程中會遞迴的構建其巢狀的Widget。
Stateful Widget
和StatelessWidget一樣,StatefulWidget也是繼承自widget類,並重寫了createElement()
方法,不同的是返回的Element
物件並不相同;另外StatefulWidget類中新增了一個新的介面createState()
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => new StatefulElement(this);
@protected
State createState();
}
複製程式碼
StatefulElement
間接繼承自Element
類,與StatefulWidget相對應(作為其配置資料)。StatefulElement
中可能會多次呼叫createState()
來建立狀態(State)物件。createState()
用於建立和Stateful widget相關的狀態,它在Stateful widget的生命週期中可能會被多次呼叫。例如,當一個Stateful widget同時插入到widget樹的多個位置時,Flutter framework就會呼叫該方法為每一個位置生成一個獨立的State例項,其實,本質上就是一個StatefulElement
對應一個State例項。
State生命週期
CounterWidget
接收一個initValue
整形引數,它表示計數器的初始值。下面我們看一下State的程式碼:
class _CounterWidgetState extends State<CounterWidget> {
int _counter;
@override
void initState() {
super.initState();
//初始化狀態
_counter=widget.initValue;
print("initState");
}
@override
Widget build(BuildContext context) {
print("build");
return Center(
child: FlatButton(
child: Text('$_counter'),
//點選後計數器自增
onPressed:()=>setState(()=> ++_counter) ,
),
);
}
@override
void didUpdateWidget(CounterWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget");
}
@override
void deactivate() {
super.deactivate();
print("deactive");
}
@override
void dispose() {
super.dispose();
print("dispose");
}
@override
void reassemble() {
super.reassemble();
print("reassemble");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
}
複製程式碼
接下來,我們建立一個新路由,在新路由中,我們只顯示一個CounterWidget
:
Widget build(BuildContext context) {
return CounterWidget();
}
複製程式碼
我們執行應用並開啟該路由頁面,在新路由頁開啟後,螢幕中央就會出現一個數字0,然後控制檯日誌輸出:
I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build
複製程式碼
可以看到,在StatefulWidget插入到Widget樹時首先initState
方法會被呼叫。
然後我們點選⚡️按鈕熱過載,控制檯輸出日誌如下:
I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget
I/flutter ( 5436): build
複製程式碼
可以看到此時initState
和didChangeDependencies
都沒有被呼叫,而此時didUpdateWidget
被呼叫。
接下來,我們在widget樹中移除CounterWidget
,將路由build
方法改為:
Widget build(BuildContext context) {
//移除計數器
//return CounterWidget();
//隨便返回一個Text()
return Text("xxx");
}
複製程式碼
然後熱過載,日誌如下:
I/flutter ( 5436): reassemble
I/flutter ( 5436): deactive
I/flutter ( 5436): dispose
複製程式碼
我們可以看到,在CounterWidget
從widget樹中移除時,deactive
和dispose
會依次被呼叫。
下面我們來看看各個回撥函式:
initState()
:當Widget第一次插入到Widget樹時會被呼叫,對於每一個State物件,Flutter framework只會呼叫一次該回撥,所以,通常在該回撥中做一些一次性的操作,如狀態初始化、訂閱子樹的事件通知等。不能在該回撥中呼叫BuildContext.inheritFromWidgetOfExactType
(該方法用於在Widget樹上獲取離當前widget最近的一個父級InheritFromWidget
,關於InheritedWidget
我們將在後面章節介紹),原因是在初始化完成後,Widget樹中的InheritFromWidget
也可能會發生變化,所以正確的做法應該在在build()
方法或didChangeDependencies()
中呼叫它。didChangeDependencies()
:當State物件的依賴發生變化時會被呼叫;例如:在之前build()
中包含了一個InheritedWidget
,然後在之後的build()
中InheritedWidget
發生了變化,那麼此時InheritedWidget
的子widget的didChangeDependencies()
回撥都會被呼叫。典型的場景是當系統語言Locale或應用主題改變時,Flutter framework會通知widget呼叫此回撥。build()
:此回撥讀者現在應該已經相當熟悉了,它主要是用於構建Widget子樹的,會在如下場景被呼叫:- 在呼叫
initState()
之後。 - 在呼叫
didUpdateWidget()
之後。 - 在呼叫
setState()
之後。 - 在呼叫
didChangeDependencies()
之後。 - 在State物件從樹中一個位置移除後(會呼叫deactivate)又重新插入到樹的其它位置之後。
- 在呼叫
reassemble()
:此回撥是專門為了開發除錯而提供的,在熱過載(hot reload)時會被呼叫,此回撥在Release模式下永遠不會被呼叫。didUpdateWidget()
:在widget重新構建時,Flutter framework會呼叫Widget.canUpdate
來檢測Widget樹中同一位置的新舊節點,然後決定是否需要更新,如果Widget.canUpdate
返回true
則會呼叫此回撥。正如之前所述,Widget.canUpdate
會在新舊widget的key和runtimeType同時相等時會返回true,也就是說在在新舊widget的key和runtimeType同時相等時didUpdateWidget()
就會被呼叫。deactivate()
:當State物件從樹中被移除時,會呼叫此回撥。在一些場景下,Flutter framework會將State物件重新插到樹中,如包含此State物件的子樹在樹的一個位置移動到另一個位置時(可以通過GlobalKey來實現)。如果移除後沒有重新插入到樹中則緊接著會呼叫dispose()
方法。dispose()
:當State物件從樹中被永久移除時呼叫;通常在此回撥中釋放資源。
狀態管理
-
如果狀態是使用者資料,如核取方塊的選中狀態、滑塊的位置,則該狀態最好由父widget管理。
-
如果狀態是有關介面外觀效果的,例如顏色、動畫,那麼狀態最好由widget本身來管理。
-
如果某一個狀態是不同widget共享的則最好由它們共同的父widget管理。
有些時候,如果不確定到底該怎麼管理狀態,那麼推薦的首選是在父widget中管理(靈活會顯得更重要一些)
總結
Flutter提供了豐富的widget,在實際的開發中你可以隨意使用它們,不要怕引入過多widget庫會讓你的應用安裝包變大,這不是web開發,dart在編譯時只會編譯你使用了的程式碼。由於Material和Cupertino都是在基礎widget庫之上的,所以如果你的應用中引入了這兩者之一,則不需要再引入flutter/widgets.dart
了,因為它們內部已經引入過了。