通过上一篇的介绍,我们知道了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的各个过程
创建

更新

重建

销毁
