1 三种树
Flutter的UI系统包含三棵树:Widget树、Element树、渲染树。他们的依赖关系是:Element树根据Widget树生成,而渲染树又依赖于Element树。 Widget有个createElement方法,返回一个Element对象。 Element有个renderObject方法,返回一个RenderObject对象。
2 加载过程
现在我们重点看一下Element,Element的生命周期如下:
1、Framework 调用Widget.createElement 创建一个Element实例,记为element,widget将作为element的初始化配置。 2、Framework 调用 element.mount(parentElement,newSlot) ,mount方法中首先调用element所对应Widget的createRenderObject方法创建与element相关联的RenderObject对象,然后调用element.attachRenderObject方法将element.renderObject添加到渲染树中插槽指定的位置(这一步不是必须的,一般发生在Element树结构发生变化时才需要重新attach)。插入到渲染树后的element就处于“active”状态,处于“active”状态后就可以显示在屏幕上了(可以隐藏)。 3、当有父Widget的配置数据改变时,同时其State.build返回的Widget结构与之前不同,此时就需要重新构建对应的Element树。为了进行Element复用,在Element重新构建前会先尝试是否可以复用旧树上相同位置的element,element节点在更新前都会调用其对应Widget的canUpdate方法,如果返回true,则复用旧Element,旧的Element会使用新Widget配置数据更新,反之则会创建一个新的Element。Widget.canUpdate主要是判断newWidget与oldWidget的runtimeType和key是否同时相等,如果同时相等就返回true,否则就会返回false。根据这个原理,当我们需要强制更新一个Widget时,可以通过指定不同的Key来避免复用。 4、当有祖先Element决定要移除element 时(如Widget树结构发生了变化,导致element对应的Widget被移除),这时该祖先Element就会调用deactivateChild 方法来移除它,移除后element.renderObject也会被从渲染树中移除,然后Framework会调用element.deactivate 方法,这时element状态变为“inactive”状态。 5、“inactive”态的element将不会再显示到屏幕。为了避免在一次动画执行过程中反复创建、移除某个特定element,“inactive”态的element在当前动画最后一帧结束前都会保留,如果在动画执行结束后它还未能重新变成“active”状态,Framework就会调用其unmount方法将其彻底移除,这时element的状态为defunct,它将永远不会再被插入到树中。 6、如果element要重新插入到Element树的其它位置,如element或element的祖先拥有一个GlobalKey(用于全局复用元素),那么Framework会先将element从现有位置移除,然后再调用其activate方法,并将其renderObject重新attach到渲染树。
3 BuildContext
StatelessWidget和StatefulWidget的build方法都会传一个BuildContext对象:
Widget build(BuildContext context) {}
我们也知道,在很多时候我们都需要使用这个context 做一些事,比如:
Theme.of(context) //获取主题
Navigator.push(context, route) //入栈新路由
Localizations.of(context, type) //获取Local
context.size //获取上下文大小
context.findRenderObject() //查找当前或最近的一个祖先RenderObject
这是一个抽象接口类,而build调用是发生在StatelessWidget和StatefulWidget对应的StatelessElement和StatefulElement的build方法中。
class StatelessElement extends ComponentElement {
...
@override
Widget build() => widget.build(this);
...
}
发现build传递的参数是this,很明显!这个BuildContext就是StatelessElement。同样,我们同样发现StatefulWidget的context是StatefulElement。但StatelessElement和StatefulElement本身并没有实现BuildContext接口,它们是间接继承自Element类,在Element类实现了BuildContext接口
class Element extends DiagnosticableTree implements BuildContext {
...
}
即BuildContext就是Widget对应的Element,所以我们可以通过context在StatelessWidget和StatefulWidget的build方法中直接访问Element对象。比如我们获取主题数据的代码Theme.of(context)内部正是调用了Element的dependOnInheritedWidgetOfExactType()方法。
4 更新
对于StatefulWIdget来说,通常会有一个类继承State,定义如下:
abstract class State<T extends StatefulWidget> with Diagnosticable {
}
需要更新内容时,调用setState方法,参数是一个block,在里面block会先执行, 然后再调用
_element.markNeedsBuild()
其内部设置_dirty = true;
An instantiation of a [Widget] at a particular location in the tree.
Widgets describe how to configure a subtree but the same widget can be used to configure multiple subtrees simultaneously because widgets are immutable. An [Element] represents the use of a widget to configure a specific location in the tree. Over time, the widget associated with a given element can change, for example, if the parent widget rebuilds and creates a new widget for this location. /// Elements form a tree. Most elements have a unique child, but some widgets (e.g., subclasses of [RenderObjectElement]) can have multiple children.
Elements have the following lifecycle:
- The framework creates an element by calling [Widget.createElement] on the
widget that will be used as the element’s initial configuration. - The framework calls [mount] to add the newly created element to the tree
at a given slot in a given parent. The [mount] method is responsible for inflating any child widgets and calling [attachRenderObject] as necessary to attach any associated render objects to the render tree. - At this point, the element is considered “active” and might appear on
screen. - At some point, the parent might decide to change the widget used to
configure this element, for example because the parent rebuilt with new state. When this happens, the framework will call [update] with the new widget. The new widget will always have the same [runtimeType] and key as old widget. If the parent wishes to change the [runtimeType] or key of the widget at this location in the tree, it can do so by unmounting this element and inflating the new widget at this location. - At some point, an ancestor might decide to remove this element (or an
intermediate ancestor) from the tree, which the ancestor does by calling [deactivateChild] on itself. Deactivating the intermediate ancestor will remove that element’s render object from the render tree and add this element to the [owner]'s list of inactive elements, causing the framework to call [deactivate] on this element. - At this point, the element is considered “inactive” and will not appear
on screen. An element can remain in the inactive state only until the end of the current animation frame. At the end of the animation frame, any elements that are still inactive will be unmounted. - If the element gets reincorporated into the tree (e.g., because it or one
of its ancestors has a global key that is reused), the framework will remove the element from the [owner]'s list of inactive elements, call [activate] on the element, and reattach the element’s render object to the render tree. (At this point, the element is again considered “active” and might appear on screen.) - If the element does not get reincorporated into the tree by the end of
the current animation frame, the framework will call [unmount] on the element. - At this point, the element is considered “defunct” and will not be
incorporated into the tree in the future.
|