通过上一篇的介绍,我们知道了Widget
本质上是UI的配置(静态、不可变),当Widget
被加载时,它的Element
对象首先会被创建,而后充当“大脑”和“仓库”的作用,最后将Widget
的配置信息转化到对应RenderObject
对象内。
Element的职责
管理
Widget
的加载、生命周期、更新等流程;RenderObject
的创建、更新等时机;child的加载、更新,都是通过Element
实现或者执行的。
例如updateChild
用于更新child的配置信息;inflateWidget
用于创建Widget
的Element
对象;atachRenderObject
方法将Element
与RenderObject
关联,并将RenderObject
插入RenderObject
树中。
仓库
Element中保存着很多信息,如常用的StatefulWidget
的State
,就是在StatefulElement
中初始化时被创建并保存的,从而实现了跨Widget
的状态恢复功能。
RenderObject
获取也是通过Element
实现的。之前说到过BuildContext
就是Element
,通过BuildContext
就可以快速获取到Widget
、State
、RenderObject
等储存在Element
中的信息。
Element的类型
如图所示,Element可分为两类
- Component Element: 组合型Element;Component Widget、Proxy Widget对应的Element都是这一类型,特点是子节点对应的Widget需要通过
build
方法去创建,同时,该类型Element都只有一个子节点 - Render Element:渲染型Element;对应Render Widget。
Element与其他元素的关系
- Element通过parent、child指针形成Element Tree
- Element持有Widget、RenderObject
- State 是绑定在 Element上的
Element的生命周期
- 构造函数
在Widget
篇中讲过,Widget
定义了createElement
方法,并将自身传入。在Element
构造方法中接收到widget
参数,并将其保存,对外暴露widget
属性来访问到widget
1 | Element(Widget widget) |
- 在UI初次创建、UI刷新时新老Widget不匹配时,parent通过
Element.inflateWidget
->Widget.createElement
创建child Element。
1 |
|
- parent通过
Element.inflateWidget
->newChild.mount()
将新创建的child插入Element tree中指定的插槽处。
1 |
|
- 由于状态更新、UI变化等,element 所在位置的 Widget可能发生了变化,此时 parent 会调用
Element.update
去更新子节点,update 会以当前节点为根节点的子树上递归进行,直到叶子节点。
1 |
|
- 如果状态更新时,element被移除,此时parent将调用
deactivateChild
方法- 从 element tree 中移除该 element
- 将相应的 render object 从 object tree 上移除
- 将 element添加到
owner._inactiveElements
中,在添加过程中会对 以该element为根节点的子树上的所有节点调用deactivate
方法。
1 |
|
此时 element 处于 inactive 状态,并从屏幕消失,该状态一直持续到当前帧动画结束
从element进入 inactive 状态到当前帧动画结束期间,还有被“抢救”的机会,前提是带有
Global Key
&& 被重新插入树中,此时- 该 element 会从
owner._inactiveElements
中移除 - 对该 element subtree 上所有节点调用
active
方法 - 将相应的「render object」重新插入「render tree」中;
- 该 element subtree 又进入 “active” 状态,并将再次出现在屏幕上。
- 该 element 会从
对于所有在当前帧动画结束时未能成功『抢救』回来的「Inactive Elements」都将被 unmount;
1 |
|
至此,element 生命周期圆满结束。
Element的重要方法
updateChild
父节点通过该方法来修改子节点对应的Widget
1 |
|
update
1 |
|
基类中的update
很简单,只是对_widget
赋值。
StatelessElement
父类
ComponenetElement
没有重写该方法
1 | void update(StatelessWidget newWidget) { |
StatefulElement
1 | void update(StatefulWidget newWidget) { |
ProxyElement
1 | void update(ProxyWidget newWidget) { |
ProxyElement.update方法需要关注的是对updated的调用,其主要用于通知关联对象 Widget 有更新。
具体通知逻辑在子类中处理,如:InheritedElement会触发所有依赖者 rebuild (对于 StatefulElement 类型的依赖者,会调用State.didChangeDependencies)。
ProxyElement 的build操作很简单:直接返回widget.child。
RenderObjectElement
1 | void update(covariant RenderObjectWidget newWidget) { |
mount
当 Element 第一次被插入「Element Tree」上时,调用该方法。由于此时 parent 已确定,故在该方法中可以做依赖 parent 的初始化操作。经过该方法后,element 的状态从 “initial” 转到了 “active”。
1 |
|
我们通过GlobalKey可以获取widget的renderObject,是在
Element.mount
的时候注册了传入的GlobalKey
,保存在Map<GlobalKey, Element> _registry
中。
ComponentElement
1 | void mount(Element parent, dynamic newSlot) { |
RenderObjectElement mount
1 | void mount(Element parent, dynamic newSlot) { |
小结
至此,对Element已经基本介绍完毕,里面的方法实在是有些多,下面用几张图片来更加清晰的认识Element的各个过程
创建
更新
重建
销毁