在Flutter中,Widget
的功能是“描述一個UI元素的配置數據”,即,Widget
其實並不是表示最終繪製在設備屏幕上的顯示元素,而只是顯示元素的一個配置數據。Flutter中真正代表屏幕上顯示元素的類是Element
。
若類比於編程語言,Widget
就像是一個抽象類,而Element
纔是具體的類實例。
因此,一個Widget
對象可能會對應多個Element
對象。
渲染流程
- 根據用戶代碼創建Widget樹。
- 由Widget樹創建對應的Element樹。
- 由Element樹生成Render樹。
最後,Render樹經過佈局、繪製等,在界面上顯示出來。
由於具體的顯示都是Element控制,因此可以認爲Element是顯示在界面上的元素。
Element的複用
出於效率原因,Element是可複用的。
對於新的一幀,Widget會判斷每個Element是否可複用。若是,則保留Element,並用新的Widget配置來更新;否則,則創建新的Element。
也就是說,對於一幀新畫面上的Element,要麼更新,要麼重建。
Widget負責判斷Element是否可複用。Widget的判斷源碼爲:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
判斷條件很簡單,根據前後幀的兩個Widget配置:
- runtimeType相等
- key相等
若兩個條件都爲true,則表示Element可複用,只使用newWidget來更新Element即可;否則,則表示Element不可複用,需直接創建。
其中:
- runtimeType爲運行時類型,即該Widget的類。例如,1/2/3的runtimeType爲int,“1”/“2”/"3"的runtimeType爲String,自定義的Widget其runtimeType爲具體的自定義類型。僅僅指類型,不涉及具體的數據。
- key即用戶傳入的key。若用戶沒有傳入,則爲null。(null == null)返回true。
實例: StatelessWidget
定義一個StatelessWidget
:
class StatelessW extends StatelessWidget {
String text = '';
StatelessW(this.text, {Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(text);
}
}
如上,該StatelessW
接收1個父組件傳入的String。
進行如下操作:
-
StatelessW sw = StatelessW(‘text1’),並放在某個容器中。框架的對應邏輯爲:
- 爲Widget設置數據text = ‘text1’。
- 該Widget對應的Element不存在,故會創建新的Element。
此時,界面上顯示文本’text1’。
-
sw = StatelessW(‘text2’)。框架的對應邏輯爲:
-
爲Widget設置數據text = ‘text2’。
-
該Widget對應的Element存在,於是進入
canUpdate()
判斷:- oldWidget.runtimeType爲
StatelessW
,newWidget.runtimeType爲StatelessW
,相等 - oldWidget.key爲null,newWidget.key爲null,相等
於是,Element判定爲可複用,保留。
- oldWidget.runtimeType爲
-
使用newWidget來更新複用的Element。由於newWidget中text = ‘text2’,故Element要顯示的text屬性會被更新爲’text2’。
此時,界面上顯示文本’text2’。
-
-
sw = StatelessW(‘text3’, key: UniqueKey())。框架的對應邏輯爲:
-
爲Widget設置數據text = ‘text3’。
-
該Widget對應的Element存在,於是進入
canUpdate()
判斷:- oldWidget.runtimeType爲
StatelessW
,newWidget.runtimeType爲StatelessW
,相等 - oldWidget.key爲null,newWidget.key爲一個隨機值,不相等
於是,Element判定爲可不復用,需創建新的Element。
- oldWidget.runtimeType爲
-
創建新的Element。由於newWidget中text = ‘text3’,故新的Element中要顯示的text屬性是’text3’。
此時,界面上顯示文本’text3’。
-
實例: StatefulWidget
定義一個StatefulWidget
:
class StatefulW extends StatefulWidget {
String text = '';
StatefulW(this.text, {Key key}) : super(key: key);
@override
_StatefulWState createState() => _StatefulWState(text);
}
class _StatefulWState extends State<StatefulW> {
String sText = '';
_StatefulWState(this.text) {
this.sText = widget.text;
}
@override
Widget build(BuildContext context) {
return Text(sText);
}
}
如上,該StatefulW
接收1個父組件傳入的String。
進行如下操作:
-
StatefulW sw = StatefulW(‘text1’),並放在某個容器中。框架的對應邏輯爲:
- 爲Widget設置數據text = ‘text1’。
- 該Widget對應的Element不存在,故會創建新的Element。
- Element創建時,內部的_StatefulWState會一同創建。_StatefulWState內部定義了一個sText,創建時會被賦予初值’text1’。
顯示依賴的是_StatefulWState。此時,界面上顯示文本’text1’。
-
sw = StatefulW(‘text2’)。框架的對應邏輯爲:
-
爲Widget設置數據text = ‘text2’。
-
該Widget對應的Element存在,於是進入
canUpdate()
判斷:- oldWidget.runtimeType爲
StatefulW
,newWidget.runtimeType爲StatefulW
,相等 - oldWidget.key爲null,newWidget.key爲null,相等
於是,Element判定爲可複用,保留。其內部的_StatefulWState也隨之保留。
- oldWidget.runtimeType爲
-
使用newWidget來更新複用的Element。由於newWidget中text = ‘text2’,故Element的text屬性會被更新爲’text2’。
-
然而,_StatefulWState被保留,故不會再次進入構造函數。於是_StatefulWState中的sText屬性依然爲’text1’。
顯示依賴的是_StatefulWState。此時,儘管StatefulW的text屬性已經更新爲’text2’,但_StatefulWState中的sText屬性依然爲’text1’,故界面上顯示文本’text1’。
-
-
sw = StatefulW(‘text3’, key: UniqueKey())。框架的對應邏輯爲:
-
爲Widget設置數據text = ‘text3’。
-
該Widget對應的Element存在,於是進入
canUpdate()
判斷:- oldWidget.runtimeType爲
StatefulW
,newWidget.runtimeType爲StatefulW
,相等 - oldWidget.key爲null,newWidget.key爲一個隨機值,不相等
於是,Element判定爲可不復用,需創建新的Element。
- oldWidget.runtimeType爲
-
創建新的Element。由於newWidget中text = ‘text3’,故新的Element中text屬性是’text3’。
-
Element創建時,內部的_StatefulWState會一同創建。_StatefulWState內部定義了一個sText,創建時會被賦予初值’text3’。
顯示依賴的是_StatefulWState。此時,界面上顯示文本’text3’。
-