从宏观意义来看build,有两种调用时机:
- 树构建(启动应用时):runApp() -> scheduleAttatchRootWidget(), 它会构建三棵树。
- 树更新 (帧绘制与更新时):不会重新构建三棵树,而是更新dirty区域的Element。
首先,我们强调两点,明确Widget和Element的功能职责:
- Widget的功能是 描述一个UI元素的配置数据
- Flutter中真正代表屏幕上显示元素的类是Element
三棵树?渲染流程是这样的:
- 根据用户代码创建Widget树
- 由Widget树创建对应的Element树
- 由Element树生成Render树
Widget与Element的关系
widget树可看作配置参数,根据widget配置转化生成Element,Element维护widget与render,简单的调用顺序如下:
- widget.build
- widget.createElement
- element.mount【当前的Element添加到Element Tree中,然后将RenderObject添加到RenderObject Tree中】
- element.attachRenderObject 【把子widget渲染出来】
下面我们来些与开发相关干货:
1. BuildContext是什么?有全局的Context吗?
我们在开发中常常会遇到widget中有build方法,里面的入参是个BuildContext
class DemoWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}
进入源码中,我们发现,其实BuildContext是个抽象类,被Element实现
class StatelessElement extends ComponentElement {
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this); //注意:build回传的参数是Element自己
@override
void update(StatelessWidget newWidget) {
...省略
}
}
abstract class Element extends DiagnosticableTree implements BuildContext {
所以,widget与context是一一对应的,context间的联系就是各自Element间的联系,context是链接的,并且组成了一个context树。
如此,由子Context中我们很容易找到一个ancestor(= parent)Widget,如:
context.ancestorWidgetOfExactType(Scaffold)=>通过从Text Context转到树结构来返回第一个Scaffold。
结论:android存在 applicationContext 与 context,applicationContext 可作为全局Context保存。Flutter 虽然可以通过globalKey保存根布局的context【专属意义】,但意义是不一样的,flutter中的context是与widget一一对应的。
扩展:对于路由跳转,我们用这种方式来保存一个navigatorKey:
void main() {
runApp(MyApp());
}
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
class MyApp extends StatelessWidget {
MyApp() {
}
// This widget is the view.common.root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
);
}
}
使用方法:
BuildContext context = navigatorKey.currentState.overlay.context
注意:通过这种方式获取的context在某些情况下需要放在
Future.delayed(Duration(seconds: 0)).then((onValue) { });
2. Element的复用
出于效率原因,Element是可复用的。对于新的一帧,Widget会判断每个Element是否可复用。若是,则保留Element,并用新的Widget配置来更新,否则,创建新的Element。
是否复用Element树:
- Widget.canUpdate //返回true,则复用旧Element
- canUpdate (runtimeType和key是否相同)
- 旧的Element会使用新Widget配置数据更新
实例分析:
定义一个StatefulWidget:
class SFDemo extends StatefulWidget {
String text = '';
SFDemo(this.text, {Key key}) : super(key: key);
@override
_SFDemoState createState() => _SFDemoState(text);
}
class _SFDemoState extends State<SFDemo> {
String sText = '';
_SFDemoState(this.text) {
this.sText = widget.text;
}
@override
Widget build(BuildContext context) {
return Text(sText);
}
}
- SFDemo sf = SFDemo("value1"),并放在某个容器中,框架的对于逻辑为:
- 为Widget设置数据text = ‘value1’
- 该Widget对应的Element不存在,故会创建新的Element
- Element创建时,内部的_SFDemoState会一同创建。_SFDemoState内部定义了一个sText,创建时会被赋予初值’value1’显示依赖的是_SFDemoState。此时,界面上显示文本’value1’
- sf = SFDemo("value2"),框架的对于逻辑为:
- 为Widget设置数据text = ‘value2’
- 该Widget对应的Element存在,于是进入canUpdate()判断:
- oldWidget.runtimeType为StatefulW,newWidget.runtimeType为StatefulW,相等
- oldWidget.key为null,newWidget.key为null,相等
- 使用newWidget来更新复用的Element。由于newWidget中text = ‘value2’,故Element的text属性会被更新为’value2’
- 然而,_SFDemoState被保留,故不会再次进入构造函数。于是_SFDemoState中的sText属性依然为’value1
显示依赖的是_SFDemoState。此时,尽管SFDemo的text属性已经更新为’value2’,但_SFDemoState中的sText属性依然为’value1’,故界面上显示文本’value1’
处理方法
- 使用UniqueKey:
sf = SFDemo(‘value2’, key: UniqueKey())
- state的ini方法由于复用不会被调用执行,但是build方法会被didUpdateWidget调用触发:
代码修改为
Widget build(BuildContext context) {
return Text(${widget.text});//直接读取widget中的参数,因为widget中的参数会被更新到
}
对于widget各个生命周期的触发方法请考虑:https://www.jianshu.com/p/b7b270e86497
网友评论