在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判定为可复用,保留。
-
使用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。
-
创建新的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也随之保留。
-
使用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。
-
创建新的Element。由于newWidget中text = ‘text3’,故新的Element中text属性是’text3’。
-
Element创建时,内部的_StatefulWState会一同创建。_StatefulWState内部定义了一个sText,创建时会被赋予初值’text3’。
显示依赖的是_StatefulWState。此时,界面上显示文本’text3’。