flutter-Widget Element Rende
flutter的理念是: 一切都是Widget(Everything is Widget)。
我们在开发Flutter app的时候主要都是在写很多Widget。但是,Widget并不是我们认为的view,因为它并不是真正的渲染对象 。事实上在 Flutter 中渲染是经历了这样的过程:
根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。
首先, 简单描述一下三者的关系:
- Widget 只是 Element 的一个配置描述 ,告诉 Element 这个实例如何去渲染。
- Widget 和 Element 之间是一对多的关系 。这是因为同一个Widget对象可以被添加到UI树的不同部分,而真正渲染时,UI树的每个Element节点都会对应一个Widget对象 (实际上渲染树是由 Element 实例的节点构成的树,而作为配置文件的 Widget 可能被复用到树的多个部分,对应产生多个 Element 对象)。
- RenderObject源码注释写着 An object in the render tree 可以看出到 RenderObject 才是实际的渲染对象,而通过 Element 源码我们可以看出:Element 持有 RenderObject 和 Widget。
(其实,查看源码可知 Element分为三类:RenderObjectElement、ComponentElement、_NullElement,仅当子类是RenderObjectElement时,才具备RenderObject.本文涉及到的element的讲述,仅仅是RenderObjectElement)
所以,element作为widget和renderObject的桥梁,还是有必要研究一下的. 结合element生命周期状态,我们来看一下element的工作:
enum _ElementLifecycle {
initial,
active,
inactive,
defunct,
}
1. Framework 调用Widget.createElement 创建一个Element实例;完成了createElement后Element的状态为initial。
- 创建element的时候需要传入一个widget,这样element中就有了Widget的实例
widget中需要提供创建element的方法
StatelessWidget实现createElement方法.png
StatefuleWidget实现createElement方法.png
2.Framework 调用 element.mount(parentElement,newSlot); 之后,Element的状态已经被改为active。
element中的mount方法.png RenderObjectElement中的mount方法.pngRenderObjectWidget中的createRenderObject方法.png
image.png
-
Elment本身的mount方法只有生命周期的改变;在RenderObjectElement的mount方法中,首先调用elment所对应Widget的createRenderObject方法创建与element相关联的RenderObject对象;(这时候,element就有自己对应的renderObject了;并且,由以上代码可见, createRenderObject方法中的BuildContext 即为 element.)
element.attachRenderObject方法.png
-
因为Element本身不进行layout,所以Elment本身的mount方法只有生命周期的改变,那么layout是在哪里进行的呢?打开RenderObjectElement的源码,看到mount方法,通过调用attachRenderObject方法将render object 添加到 render tree中,在此,实现了Element的Layout。
element.attachRenderObject方法将element.renderObject添加到渲染树中插槽指定的位置. 【这一步不是必须的,一般发生在Element树结构发生变化时才需要重新attach】插入到渲染树后的element就处于“active”状态,处于“active”状态后就可以显示在屏幕上了(可以隐藏)。
image.png
3.当element父Widget的配置数据改变时,为了进行Element复用,Framework在决定重新创建Element前会先尝试复用相同位置旧的element
-
调用Element对应Widget的canUpdate()方法,如果返回true,则复用旧Element,旧的Element会使用新的Widget配置数据更新,反之则会创建一个新的Element,不会复用。
Widget.canUpdate()主要是判断newWidget与oldWidget的runtimeType和key是否同时相等,如果同时相等就返回true,否则就会返回false。根据这个原理,当我们需要强制更新一个Widget时,可以通过指定不同的Key来禁止复用。【这就是我们常见的key的作用】
image.png
image.png
image.png
4.当有祖先Element决定要移除element 时(如Widget树结构发生了变化,导致element对应的Widget被移除),这时该祖先Element就会调用deactivateChild 方法来移除它,移除后element.renderObject也会被从渲染树中移除,然后Framework会调用element.deactivate 方法,这时element状态变为“inactive”状态。
image.png5.“inactive”状态的element将不会再显示到屏幕。
-
为了避免在一次动画执行过程中反复创建、移除某个特定element,“inactive”态的element在当前动画最后一帧结束前都会保留,如果在动画执行结束后它还未能重新变成”active“状态,Framework就会调用其unmount方法将其彻底移除,这时element的状态为defunct,它将永远不会再被插入到树中。
image.png
6.如果element要重新插入到Element树的其它位置,如element或element的祖先拥有一个GlobalKey(用于全局复用元素),那么Framework会先将element从现有位置移除,然后再调用其activate方法,并将其renderObject重新attach到渲染树。
image.png7.BuildContext即element
image.png image.pngimage.png
image.png
例子
import 'package:flutter/material.dart';
class ElementTestPage extends StatelessWidget {
const ElementTestPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: MyWidget(),
);
}
}
class MyWidget extends Widget {
@override
MyElement createElement() => MyElement(this);
///完成了createElement后Element的状态为initial。
}
class MyElement extends ComponentElement {
int _counter = 0;
MyElement(Widget widget) : super(widget);
@override
void mount(Element parent, dynamic newSlot) {
print('当前element生命周期_ElementLifecycle.initial');
super.mount(parent, newSlot);
print('触发mount生命周期===当前element生命周期_ElementLifecycle.active');
}
@override
void deactivate() {
print('当前element生命周期_ElementLifecycle.active');
super.deactivate();
print('触发deactivate生命周期===当前element生命周期_ElementLifecycle.inactive');
}
@override
void unmount() {
print('当前element生命周期_ElementLifecycle.inactive');
super.unmount();
print('触发unmount生命周期===当前element生命周期_ElementLifecycle.defunct');
}
@override
Widget build() {
print("触发build");
return Column(
children: [
Text('我只是一个显示控件$_counter'),
FloatingActionButton(
heroTag: "get_test1_increment",
onPressed: () {
_counter++;
markNeedsBuild(); //点击后将该Element标记为dirty,Element将会rebuild
},
child: Icon(Icons.add),
)
],
);
}
}
查看源码可知, setState里面调用的也是:
_element!.markNeedsBuild();
上述,我们知道BuildContext即widget对应的element, 利用element. markNeedsBuild()可以让widget重新执行build方法. 所以,我们想 在statelesswidget里面 如果想改变widget实现重新build的话,是不是也可以呢? 答案是可以的.(代码如下)
(不过,如果我们通过Statelesswidget的element.markNeedsBuild() 直接重新build的话,BuildContext只是在build方法内存在,每次都会更新,且无法设置成成员变量,操作复杂. 所以不建议这么操作)
class ElementTestPage extends StatelessWidget {
const ElementTestPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print("context is Element----${context is Element}");
Element element = context as Element;
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
Text('我只是一个显示控件'),
FloatingActionButton(
heroTag: "get_test1_increment",
onPressed: () {
element.markNeedsBuild(); //点击后将该Element标记为dirty,Element将会rebuild
},
child: Icon(Icons.change_history),
)
],
),
);
}
}
8.看一下源码中Element的描述,简单的翻译一下
/// An instantiation of a [Widget] at a particular location in the tree.
(element是树中特定位置的[Widget]实例)
///
/// Widgets describe how to configure a subtree but the same widget can be used
/// to configure multiple subtrees simultaneously because widgets are immutable.
(widget描述如何配置子树,但是同一个widget可以用于同时配置多个子树,因为widget是不可变的。)
/// 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.
([Element]表示使用widget来配置树中的特定位置。随着时间的推移,给定元素关联的widget可能会发生变化,例如,如果父widget为该位置重建并创建新的widget。)
/// Elements form a tree. Most elements have a unique child, but some widgets
/// (e.g., subclasses of [RenderObjectElement]) can have multiple children.
(element树, 大多数element都有唯一的子元素,但是一些widget(例如,[RenderObjectElement]的子类)可以有多个子元素。)
/// Elements have the following lifecycle:
(element有以下生命周期:)
/// * The framework creates an element by calling [Widget.createElement] on the
/// widget that will be used as the element's initial configuration.
- (framework调用[Widget.createElement]创建一个element,用于初始配置widget的build方法。)
/// * The framework calls [mount] to add the newly created element to the tree
/// at a given slot in a given parent.
- (framework调用[mount]方法,将新创建的element添加到树中给定父级中的给定插槽中。)
///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.
- ([mount]方法负责渲染任何子widget并根据需要调用[attachRenderObject],以将任何关联的渲染对象renderObject附加到渲染树render tree.)
/// * At this point, the element is considered "active" and might appear on
/// screen.
(此时,元素状态是active,可能会出现在屏幕上。)
/// * 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.
- (在某个时刻,父级可能会决定更改用于配置此元素的widget,例如,因为父级使用新Sate重建。)
/// When this happens, the framework will call [update] with the new
/// widget.
(当这种情况发生时,framework将用新的小部件调用[update]。)
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.
(新widget将始终具有与旧widget相同的[runtimeType]和key。如果父级希望在树中的这个位置更改widget的[runtimeType]或key,可以通过unmounting这个元素并在这个位置inflating新的widget来完成。)
/// * 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.
- (在某个时刻,祖先可能会决定从树中删除这个元素(或中间祖先),是通过对自己调用[deactivateChild]来完成的。)
///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.
(停用中间祖先将从呈现树中删除该element的render object,并将该element添加到[owner]的非活动元素列表( inactive elements)中,从而导致framework对此element调用[deactivate]。)
/// * 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.
- (此时,该元素被认为是“非活动”的,不会出现在屏幕上。元素只能在当前动画帧结束之前保持非活动状态。在动画帧的末尾,仍处于非活动状态的任何元素都将被unmount。)
/// * 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.)
- (如果元素被重新合并到树中(例如,因为它或它的一个祖先有一个可重用的全局键),框架将从[owner]的非活动元素列表中删除该元素,对该元素调用[activate],并将该元素的呈现对象重新附加到呈现树。(此时,元素再次被视为“活动”并可能出现在屏幕上。))
/// * 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.
(如果元素在当前动画帧结束时没有重新合并到树中,框架将对元素调用[unmount]。)
/// * At this point, the element is considered "defunct" and will not be
/// incorporated into the tree in the future.
(在这一点上,元素被认为是“不存在的”,将来不会合并到树中。)