開篇
我們知道,flutter中我們做的一切幾乎都是在和 Widget
打交道,那麼它在flutter中扮演著怎樣的角色,起到了什麼樣的作用呢?
我們將通過閱讀原始碼的方式,去解答關於它的各種疑問。
而這也是flutter知識拼圖中,我們選擇的第一塊。
Widget
可以從我們常用的 StatelessWidget
和 StatefulWidget
看到,他們都擁有同一個父類 Widget
,我們將 Widget
作為起點,先看看一看它的構造
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
...
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
複製程式碼
Widget
是繼承於 DiagnosticableTree
的,關於 DiagnosticableTree
這個類,它主要用於在除錯時獲取子類的各種屬性和children資訊,在flutter各個物件中你經常能看到它,目前我們不需要去關心與之相關的內容
我們可以看到,Widget
是一個抽象類;同時它被 immutable
註解修飾,說明它的各個屬性一定是不可變的,這就是為什麼我們寫各種 Widget
時,所寫的各個屬性要加 final
的原因,否則編譯器就會發出警告
同時,我們看到了熟悉的 createElement()
方法,它交由子類去實現
還有 canUpdate(...)
方法, 如果返回 true
表示可以更新當前 Element
物件,並用新的 widget
更新 Element
的 widget
,在下一篇關於 Element
的介紹中,我們能看到它的具體使用
接下來,我們看一看幾個飛車重要的 Widget 物件
StatelessWidget
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
}
複製程式碼
StatelessWidget
也是一個抽象方法,提供了一個 build(context)
方法供子類實現,而這裡重寫的 createElement()
預設返回的是 StatelessElement(...)
物件,再來看看 StatefulWidget
StatefulWidget
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);
@protected
State createState();
}
複製程式碼
StatefulWidget
同樣是一個非常簡單的抽象物件,這裡預設提供的是建立 StatefulElement
的方法。同時,提供了我們非常熟悉的 createState()
方法,它返回一個 State
物件,我們經常使用的 setState(...)
就在其中,來看一下它的實現
State
abstract class State<T extends StatefulWidget> extends Diagnosticable {
T get widget => _widget;
_StateLifecycle _debugLifecycleState = _StateLifecycle.created;
BuildContext get context => _element;
bool get mounted => _element != null;
void initState() {
assert(_debugLifecycleState == _StateLifecycle.created);
}
void didUpdateWidget(covariant T oldWidget) { }
void reassemble() { }
void setState(VoidCallback fn) {
...
_element.markNeedsBuild();
}
void deactivate() { }
void dispose() {
...
_debugLifecycleState = _StateLifecycle.defunct;
...
}
Widget build(BuildContext context);
void didChangeDependencies() { }
}
enum _StateLifecycle {
created,
initialized,
ready,
defunct,
}
複製程式碼
為了方便閱讀,上面省去了部分程式碼和所有註解,首先可以看到,State
是 Diagnosticable
的子類,而 Diagnosticable
物件就是之前 DiagnosticableTree
中內提供的結點,我們不需要關注它。
State
中持有了 BuildContext
物件和 Widget
物件, BuildContext
是 Element
實現的介面,下一篇會講到它。同時,我們可以發現 State
是具備生命週期的,分別是四種情況:
- created: 預設的生命週期
- initialized:
initState()
被呼叫後的生命週期 - ready:
didChangeDependencies()
被呼叫後的生命週期, 當生命週期為這個時就準備呼叫build()
方法了 - defunct:
dispose()
被呼叫後的生命週期
其中第二個第三個都是在 StatefulElement
中被改變的,可以簡單的看一看
class StatefulElement extends ComponentElement {
...
@override
void _firstBuild() {
...
final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic;
...
_state._debugLifecycleState = _StateLifecycle.initialized;
...
_state.didChangeDependencies();
...
_state._debugLifecycleState = _StateLifecycle.ready;
...
}
...
@override
void unmount() {
super.unmount();
_state.dispose();
...
}
}
複製程式碼
至於哪裡呼叫了 _firstBuild()
我們放到第二篇講
還有一些方法,我們簡單介紹一下:
- didUpdateWidget(...): 當 Widget 更新時,會呼叫它。可以重寫這個方法做一些資料修改的操作,比如更新資料,但是不要在這個方法中呼叫
setState
因為這樣就重複重新整理了 - reassemble(): 這個方法是debug時熱過載會觸發的,我們一般不需要去管它
- deactivate(): 當前
Widget
從樹中移除的時候,會呼叫這個方法,它會比dispose()
先呼叫
還有 setState(...)
是我們最常用的一個方法,這個方法中並沒有觸發傳入的 VoidCallback
,而是呼叫了 Element
的 markNeedsBuild()
,這也是為什麼我們把修改資料的邏輯放到 setState(...)
前、中、後,資料都能重新整理的原因,而真正導致資料重新整理的邏輯,等第二篇就知道啦。
關於 State
的部分就差不多了。不知道這時候你是否會產生一個疑問,因為我們常常聽說 Widget
、Elmenet
、RenderObject
這三個物件的關係是十分緊密的。不過到上面只看到了 Widget
與 Element
,完全沒有 RenderObject
的身影,那麼它是在 Elmenet
中建立的嗎?
這裡我先提前說明一下,並不是。因為 Elment
中並沒有相關的 RenderObject
建立方法,到這裡問題來了,RenderObject
的建立方法,在哪裡呢?
下面將介紹另一個非常重要,但是我們很少使用的 Widget
物件
RenderObjectWidget
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key key }) : super(key: key);
@override
RenderObjectElement createElement();
@protected
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
複製程式碼
可以看到,RenderObject
的建立方法其實是由 RenderObjectWidget
提供的,同時還提供了對應的 RenderObjectElement
建立方法。關於這個 Widget
以及 RenderObject
相關資訊,留到第三篇。本篇就結束了。
總結
通過上面一番原始碼閱讀,我們可以發現 Widget
具備下面的一些特性:
Widget
中所持有的物件都是不可修改的,也就是說它不具備資料的儲存功能,而只能夠對資料進行傳遞;即便是StatefulWidget
,資料的儲存也是放在State
中,它只是提供了State
的建立方法- 每個
Widget
都提供了Element
的建立方法,但只有部分Widget
具備RenderObject
的建立方法。從這裡我們可以斷定,Element
與RenderObject
不是一一對應的關係,很可能是一對一或者多對一的關係;同時我們猜想,Element
與Widget
是一一對應的關係。
僅僅根據上面這些特性,並不能給 Widget
下一個準確的定義,那是因為我們還沒深入瞭解主要的 RenderObject
。不過這裡為了結束這篇文章,還是先提前做個定義吧:
RenderObject
用於佈局繪製等操作,那麼RenderObjectWidget
則是向RenderObject
提供繪製所需要的配置引數State
用於進行資料的儲存與修改, 那麼StatefulWidget
則是用於向State
傳遞配置引數StatelessWidget
用於向其他Widget
傳遞配置引數。
而 Widget
之間又可以相互組合,最後得出結論:Widget 用於描述描當前的配置和狀態下檢視所應該呈現的樣子