Flutter
是一个优秀的 UI 框架,借助它开箱即可用的 Widgets
我们能够快速搭建出漂亮和高性能的用户界面。那么那些 Widget
底层又是如何完成渲染的呢?
在 Flutter
中和 Widgets
一起协同工作的还有另外两个伙伴: Elements
和 RenderObjects
,由于都有着树形结构,我们常称为三棵树。
Widget
是 Flutter
的核心部分,是用户界面的不可变描述,widget
的功能也就是描述一个 UI 元素的配置数据,也就是说 Widget
并不是最终绘制到屏幕上的元素,它只是描述显示元素的一个配置而已。
事实上再代码运行的过程中并没有明确的 widget 树的概念,这棵树是我们在开发过程中对 widget 嵌套的描述,因为确实长得像一棵树所以才这样说。
abstract class Widget {
const Widget({ this.keuy });
final Key key;
/// createElement 是抽象方法,子类必须实现,该方法创建一个 Element,所以每个 Element 都会对应一个 widget 对象
Element creatElement();
/// 判断 oldWidget 和 newWidget 是不是同一个 widget, 如果 runtimeType 和 key 相同则认为是同一个 widget
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
}
}
需要注意的是 widget 不能被修改,如果修改只能重新创建,因为 widget 并不参与渲染,它只是一个配置文件而已,只需要告诉渲染层自己的样式即可。
Element
是分离 WidgetTree
和真正的渲染对象的中间层,Elements
是实例化的 Widget
对象,通过 Widget
的 createElement()
方法,在特定位置使用 Widget
配置数据生成,并且 widget
可以对应多个 Element
。这是因为同一个 Widget 可以被添加到 Element
树的不同部分。而真正渲染的时候,每一个 Element
都会对应一个 Widget
对象。
所谓的 UI 树就是有一个个 Element
节点构成。组件的最终 Layout
,渲染都是通过 RenderObject
来完成,从创建到渲染的大致流程是:根据 Widget
生成 Element
,然后再创建对应的 RenderObject
并关联到 Element.renderObject
属性上,最后通过 RenderObject
来完成布局排列和绘制。
如上说过,Element
表示一个 Widget
树中特定的实例,大多数 Element
只有唯一的 RenderObject
,但是还有一些 Element
会有多个子节点,如集成自 RenderObjectElement
一些类,比如 MultiChildRenderObjectObject
。最终所有的 Element
的 RenderObject
构成一棵树,我们称之为渲染树。
简单来讲,我们可以认为 Flutter
的 UI 系统包含了三棵树: Widget
树,Element
树,Render
树,他们的对应关系是 Element
树是根据 Widget
树生成,而 Render
树又依赖 Element
树,关系如下:
Element 类源码
abstract class Element extends DiagnosticableTree implements BuildContext {
Element(Widget widget)
: assert(widget != null),
_widget = widget;
Element _parent;
Widget get widget => _widget;
Widget _widget;
RenderObject get renderObject {...}
void mount(Element parent, dynamic newSlot){...}
void activate(){...}
void deactivate(){...}
void unmount(){...}
}
用于应用界面布局和绘制,负责真正的渲染,保存了元素大小,布局信息;另外实例化一个 RenderObject 是非常消耗性能的,下面会有详细的介绍。
简单了解了三棵树之后,那 Flutter
是如何创建和布局的呢?以及三棵树之间他们是如何协同的呢?可以通过一个示例来看:
class ThreeTree extends StatelessWidget {
Widget build(BuildContext context) {
return Contariner(
color: Colors.green,
child: Container(
color: Colors.red
)
)
}
}
上面这个简单的例子,他由三个 Widget
组成, ThreeTree
、Container
、Container
。那么当 Flutter
的 runApp()
方法被调用时候会发生什么呢?
当 runAPP()
被调用时候,第一时间底层会发生下面时间:
Flutter
会构建包含这三个 Widget
的 Widget
树;Flttter
遍历 Widget
树,然后根据其中 Widget
调用 creatElements()
来创建对象,最后将这些对象组建成 Element
树;Widget
对应的 Element
通过 creatRenderObject()
创建的 RenederObject
;下图是 Flutter经过这三个步骤后的状态:
从上图中可以看出,Flutter
创建了三个不同的树,一个对应 Wideget
,一个对应 Element
,一个对应 RenderObject
。每一个 Element
中都有着对应的 Widget
和 RenderObject
的引用。可以说 Element
是存在于可变 Widget
树和不可变 RenderObject
树之间的桥梁。在 Flutter
里面 Element
擅长比较 Widget
和 RenderObject
两个 Object
,它的作用是配置好 Widget
在树中的位置,并且保持相对应的 RenderObject
和 Widget
之间的引用。
简单来说就是为了性能,复用 Element
从而减少频繁创建和销毁 RenderObject
。因为实例化一个 RendeObject
的成本很高,频繁的实例化和销毁 RenderObject
对性能的影响很大,所以当 Widget
树改变时候,Flutter
使用 Element
树来比较新的 Widget
树和原来的 Widget
树.
_ElementLifeCycle _lifecycleState = _ElementLifecycle.initial
/// RenderObjectElement 的 mount 方法
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSolt);
//...
_renderObject = widget.createRenderObject(this);
assert(_slot == newSlot);
attachRenderObject(newSlot);
_dirty = false;
}
当 fragment
调用 element.mount
方法后,mount
方法会首先调用 element
对应的 widget
的 createRenderObject
方法来创建与 element
对应的 RenderObject
对象。
然后调用 element.attachRenderObject
将 element.renderObject
添加到渲染树插槽的位置(这一步不是必须的,一般发生在 Element
树结构发生变化时才需要重新 attach)。
插入到渲染后的 element
就处于 active
状态, 处于 active
状态后就可以显示在屏幕上了(也可以设置属性 hide 进行隐藏)。
super.mount(parent, newslot)
_lifecycleState = _ElementLifecycle.active
当 widget 更新时,为了避免重建 element
会判断是否可以更新,会调用 updateChild
方法。
// framework.dart
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
/// 没有新的 widget,并且有原来的widget,则移除原来的 child,因为他不再有配置
if(newWidget == null) {
if(child != null) {
deactivateChild(child);
return null;
}
Element newChild;
/// 原来有 child
if(child != null) {
assert((){
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
/// 如果父控件类型相同,子控件也相同,直接更新
if(hasSameSuperclass && child.widget == newWidget){
if(child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
/// 父控件类型相同,并且可以更新 widget,则更新 child
if(child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert((){
child.owner._debugElementWasRebuild(child);
return true;
}());
newChild = child;
} else {
/// 不能更新,则需要先移除原有的 child,并且创建新的 child 并添加
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
/// 没有 child,直接创建 child并添加
newChild = inflateWidget(newWidget, newSlot);
}
assert((){
if(child != null)
_debugRemoveGlobalKeyReservation(child);
final Key key = newWidget?.key;
if(key is GlobalKey) {
key._debugReserveFor(this, newChild);
}
return true;
}());
return newChild;
}
...
// 判断是否可以更新
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
}
}
widget.canUpdate
,主要判断 type 和 key 是否相同。如果我们需要强制更新,只需要修改key即可,官方不推荐修改 runtimeType。
static bool canUpdate(Widget oldWidget, Widget newWidget){
return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
}
从“非活动”到“活动”生命周期的转换
在上面的updateChild
方法中,最后如果调用了 inflateWidget()
方法后,就需要将状态从 inactive
转到 active
状态。
void activate() {
assert(_lifecycleState == _ElementLifecycle.inactive);
assert(widget != null);
assert(owner != null);
assert(depth != null);
final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies;
_lifecycleState = _ElementLifecycle.active;
// We unregistered our dependencies in deactivate, but never cleared the list.
// Since we're going to be reused, let's clear our list now.
_dependencies?.clear();
_hadUnsatisfiedDependencies = false;
_updateInheritance();
if (_dirty)
owner!.scheduleBuildFor(this);
if (hadDependencies)
didChangeDependencies();
}
从“活动”到“非活动”生命周期状态的转换
在上面updateChild
方法中,我们可以看到新的 widget
为空并且存在旧的,就会调用 deactiveChild
移除 child
, 然后调用 deactivate
方法将_lifecycleState
设置为inactive
.
void deactivate() {
assert(_lifecycleState == _ElementLifecycle.active);
assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
assert(depth != null);
if (_dependencies != null && _dependencies!.isNotEmpty) {
for (final InheritedElement dependency in _dependencies!)
dependency._dependents.remove(this);
}
_inheritedWidgets = null;
_lifecycleState = _ElementLifecycle.inactive;
}
从“非活动”到“已失效”生命周期状态的转换
void unmount() {
assert(_lifecycleState == _ElementLifecycle.inactive);
assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
assert(depth != null);
assert(owner != null);
// Use the private property to avoid a CastError during hot reload.
final Key? key = _widget!.key;
if (key is GlobalKey) {
owner!._unregisterGlobalKey(key, this);
}
// Release resources to reduce the severity of memory leaks caused by
// defunct, but accidentally retained Elements.
_widget = null;
_dependencies = null;
_lifecycleState = _ElementLifecycle.defunct;
}
Container
创建出来的是 StatelessElement
, 下面就从核心代码看一下它的调用过程。
class StatelessElement extends ComponentElement {
/// 通过 createElement 创建传入的 widget
StatelessElement(StatelessWidget widget): super(widget);
StatelessWidget get widget => super.widget as StatelessWidget;
/// 这里调用的build 就是我们自己实现的 build 方法
Widget build() => widget.build(this);
}
abstract class ComponentElement extends Element {
/// Creates an element that uses the given widget as its configuration.
ComponentElement(Widget widget) : super(widget);
Element? _child;
bool _debugDoingBuild = false;
bool get debugDoingBuild => _debugDoingBuild;
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_firstBuild();
}
void _firstBuild() {
rebuild();
}
void performRebuild() {
//.....
}
Widget build();
}
abstract class Element extends DiagnosticableTree implements BuildContext {
//构造方法,接受一个 widget 参数
Element(Widget widget)
:assert(widget != null),
_widget = widget;
Widget get widget => _widget;
Widget _widget;
void rebuild() {
if(!avtive || !dirty)
return;
Element debugPreviousBuildTarget;
/// 这里调用的 performRebuild 方法,在当前类并没有实现,只能去自己类里查找实现
performRebuild();
}
/// Called by rebuild() after the appropriate checks have been made.
void performRebuild();
}
整个过程可以分几步理解
- 这里创建了
StatelessElement
, 创建成功后,frameWork
就会调用mount
方法,因为StatelessElement
没有实现mount
, 所以这里调用的是ComponentElement
的mount
.- 在
mount
中调用了_firstBuild
方法进行第一次构建(注:这里调用的是实现类StatelessElement
的_firstBuild
方法)_firstBuild
方法最后调用的是super._firstBuild
,也就是ComponentElement
的_firstBuild
方法,最终走到了rebuild()
。由于ComponentElement
没有重写,所以最终调用的是Element
的rebuild()
方法。rebuild
最终又会调用到ComponentElement
的PerformRebuild
方法。如下:
('vm:notify-debugger-on-exception')
void performRebuild() {
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget? built;
try {
assert(() {
_debugDoingBuild = true;
return true;
}());
// 调用 build ,这里调用的是实现类 StatelessElement 的,最终是调用到我们自己实现的 build 中
built = build();
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
// catch
} finally {
_dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
}
try {
//最终调用 updateChild 方法
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
//....
_child = updateChild(null, built, slot);
}
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.finishSync();
}
('vm:prefer-inline')
/// 这个方法下面户多次提到
Element inflateWidget(Widget newWidget, Object? newSlot) {
assert(newWidget != null);
final Key? key = newWidget.key;
if (key is GlobalKey) {
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
assert(newChild._parent == null);
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild!;
}
}
//创建对应的 element
final Element newChild = newWidget.createElement();
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
// 调用 mount 方法
newChild.mount(this, newSlot);
assert(newChild._lifecycleState == _ElementLifecycle.active);
return newChild;
}
上面代码最终调用到了
udpateChild
方法,这个方法在上面 Element 的生命周期里有说到。在
updateChild
方法中就会判断build
是否需要更新或者替换,如果需要替换,就会清除原来的,并且对新的build
创建对应的的element
,并且在最后调用build
对应的Element
的mount
方法。这里的Element
就不一定是StateLessElement
了,而是看build
方法中的widget
对应的Element
是什么。从上面流程来看,整个过程像一个环,最开始的
framework
调用mount
。在mount
中最终调用到了performRebuild
,在performRebuild
中通过我们实现的build
方法,拿到对应的widget
后,如果需要转换,就会重新创建widget
的element
,并且调用这个element
的mount
方法。大致流程如下:
相比于StatelessWidget
,StatefulWidget
中多了一个State
,在 StatefulWidget
中通过creatState
来创建一个State
,如下:
abstract class StatefulWidget extend Widget {
const StatefulWidget({key? key}) : super{key:key};
StatefulElement creatElement() => StatefulElement(this);
State createState();
}
通过上面代码可以看出 StatefulWidget
对应Element
正是 StatefulElement
。
class StatefulElement extends ComponentElement {
StatefulElement(StatefulWidget widget)
:_state = widget.creatState,
super(widget){
assert(state._element == null);
state._element = this;
state._widget = widget;
}
State<StatefulWidget> get state => _state!;
State<StatefulWidget>? _state;
Widget build() => state.build(this);
}
在StatefulElement
中通过调用 widget.createState
获取到了state
对象。StatefulElement
创建完成后,framework
就会调用StatelessElement
中的mount
方法了。
和 StatelessElement 不同的是:
StatelessElement
中是通过调用 widget.build(this)
方法StatefulElement
中是通过调用 State.build(this)
方法MutiChildRenderObjectElement
是继承自 RenderObjectElement
的。使用情况如SizedBox
,Flex
.
class MultiChildRenderObjectElement extends RenderObjectElement {
void mount(Element? parent, Object? newSlot) {
//调用super.mount 将传入的 parent 插入到树中
super.mount(parent, newSlot);
final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false);
Element? previousChild;
//遍历所有的child
for (int i = 0; i < children.length; i += 1) {
//加载child
final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild));
children[i] = newChild;
previousChild = newChild;
}
_children = children;
}
}
abstract class RenderObjectElement extends Element {
void mount(Element? parent, Object? newSlot) {
//将传入的 parent 插入到树中
super.mount(parent, newSlot);
//创建与element相关联的renderObject 对象
_renderObject = widget.createRenderObject(this);
//将element.renderObject 插入到渲染树中的指定位置
attachRenderObject(newSlot);
_dirty = false;
}
}
大致步骤如下:
MutiChildRenderObejectElement
的 mount
方法中,先将传入的 parent
插入到树中RenderObjectElement
中创建一个 renderObject
并添加到渲染树中的插槽的指定位置MultiChildRenderObejectElement
中遍历所有的 child
,并且调用 inflatWidget
创建 child
并插入指定插槽中。总结:
通过上面的分析,知道了 Element
的生命周期以及他的调用过程。并且如果仔细观察那三个 Element
就会发现,上面的三种 Element
可以分为两类,分别是组合类和绘制类。
组合类一般继承自 StatelessElement
或者 SatefulElement
,他们属于组合类,并不参与绘制,内部嵌套了很多层 Widget
,查看他们的 mount
方法,就会发现其中并没有创建 renderObject
并添加到渲染树。例如 StatelessWidget
,StatefulWidget
, Text
, Container
, Image
等。
绘制类就是参加了绘制,在 mount
方法中,会有创建 renderObject
并 attachRenderObject
到渲染树中。如 Coloum
, SizedBox
等。
还有一种不常用的类,代理类 (inheritedWidget),对应的
Element
是ProxyElement
。
上面每提到一个 Element
都对应一个 RenderObject
,我们可以通过 Element.renderObject
来获取。并且 RenderObeject
的主要职责是Layout
,所有 RenderObject
会组成一个渲染树 RenderTree
。
通过上面的分析,可以看到树的核心就是 mount
方法。来看下 RenderObjectElement.mount
方法。
void mount(Element? parent, Object? newSolt) {
super.mount(parent, newSlot);
_renderObject = widget.creatRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
在执行完 super.mount
之后(将 parent
插入到 element
树中),执行了 attachRenderObject
方法。
void attachRenderObject(Object? newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
//查询当前最近的 RenderObject 对象
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
// 将当前节点的 renderObject 对象插入到上面找的的 RenderObject下面
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
//.........
}
RenderObjectElement? _findAncestorRenderObjectElement() {
Element? ancestor = _parent;
while (ancestor != null && ancestor is! RenderObjectElement)
ancestor = ancestor._parent;
return ancestor as RenderObjectElement?;
}
上面代码是一个死循环,退出的时机就是 ancestor
父节点为空和父节点是 RenderObjectElement
。说明这个方法会找到离当前节点最近的一个 RenderObjectElement
对象,然后调用 insertRenderObjectChild
方法,这个方法是一个抽象方法,可以看下里面的实现:
SingleChildRenderObjectElement
void insertRenderObejectChild(RenderObeject child, Object? slot) {
final RenderObejectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;
renderObject.child = child;
}
上面代码中,找到当前
RenderOjectElement
的renderObject
, 且将我们传入的child
给了renderObject
的child
. 所以这样就将传入的child
挂在了RenderObject
树上。
MultiChildRenderObjectElement
void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) {
final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
assert(renderObeject.debugValidateChild(child));
renderObejct.insert(child,after:slot.value?.renderObject);
assert(renderObject == this.renderObject);
}
上面代码中,找到
renderObject
之后赋值给了ContainerRenderObejctMixin
, 可以看下ContainerParentDataMixin
在这个类
// 具有子项列表的渲染对象的通用混合
// 为具有双向链接的子项列表的渲染对象子类提供子模型
mixin ContainerRenderObjectMixin<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>> on RenderObject {}
泛型
mixin
用于渲染一组子对象的对象。MultiChildRenderObjectElement
是有子列表的。
通过上面的注释可以看到一个词双向链表。所以 MultiChildRenderObjectElement
的子节点通过双向链表链接。上面的 insert
最终会调用到 _insertIntoChildList
方法,如下:
ChildType? _firstChild;
ChildType? _lastChild;
void _insertIntoChildList(ChildType child, { ChildType? after }) {
final ParentDataType childParentData = child.parentData! as ParentDataType;
_childCount += 1;
assert(_childCount > 0);
if (after == null) {
// after 为 null,则插入到 _firstChild 中
childParentData.nextSibling = _firstChild;
if (_firstChild != null) {
final ParentDataType _firstChildParentData = _firstChild!.parentData! as ParentDataType;
_firstChildParentData.previousSibling = child;
}
_firstChild = child;
_lastChild ??= child;
} else {
final ParentDataType afterParentData = after.parentData! as ParentDataType;
if (afterParentData.nextSibling == null) {
// insert at the end (_lastChild); we'll end up with two or more children
// 将 child 插入到末尾
assert(after == _lastChild);
childParentData.previousSibling = after;
afterParentData.nextSibling = child;
_lastChild = child;
} else {
// insert in the middle; we'll end up with three or more children
// 插入到中间
childParentData.nextSibling = afterParentData.nextSibling;
childParentData.previousSibling = after;
// set up links from siblings to child
final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling!.parentData! as ParentDataType;
final ParentDataType childNextSiblingParentData = childParentData.nextSibling!.parentData! as ParentDataType;
childPreviousSiblingParentData.nextSibling = child;
childNextSiblingParentData.previousSibling = child;
assert(afterParentData.nextSibling == child);
}
}
}
根据上面的注释我们可以将其分为三部分,
after
为 null
的时候将 child
插入到第一个节点,child
插入到末端,child
插入到中间。以一个示例查看:
Column(
child:[
SizedBox(..),
Text(data),
Text(data),
Text(data),
],
)
第一个 Stack
向上找到 Column(RenderObjectElement)
之后,调用这个方法,目前 after
为 null
,则 _firstchild
就是 SizedBox
(对应的 renderObejct
是 RenderConstraineBox
);
第二个 Text
,前面说过Text
是组合类型,所以它不会挂载到树中,通过查询源码看出最终的 text
用的是 RichText
。 RichText
向上查找到 Column(RenderObjectElement)
之后,调用这个方法,出入两个参数,第一个 child
是 RichRext
对应的 RenderParagraph
, 第二个 after
是 SizedBox
对应的 RenderConstrainedBox
. 按照上面的逻辑,执行下面代码:
final ParentDataType afterParentData = after.parentData! as ParentDataType;
if (afterParentData.nextSibling == null) {
// insert at the end (_lastChild); we'll end up with two or more children
assert(after == _lastChild);
childParentData.previousSibling = after;
afterParentData.nextSibling = child;
_lastChild = child;
}
将 child
的 childParentData.previousSibling
指向第一个节点,将第一个接的 afterParentData.nextSibling
指向 child
,最后让 _lastchild
执行 child
。
后面也是如此,当流程结束后就可以得到 RenderTree
。
如果某一个位置的 Widget
和 新 Widget
不一致,才需要重新创建 Element
;
如果某一位置 Widget
和新 Widget
一致时候(两个 widget
相等或者 runtimeType
与 key
相等),则只需修改 RenderObject
的配置,不用进行耗时耗性能的 RenderObject
的实例化。
因为 Widget
是非常轻量级的,实例化耗费性能很少,所以用它描述 APP
的状态(也就是configuration
)的最好工具;
重量级的 RenderObject
(创建十分耗性能)则需要尽可能的少创建,并尽可能复用;
在框架中, Element 是被抽离出来的,所以我们不需要经常和他们打交道,每个 Widget 的 build (BuildContext context)方法中传递的 context 就是实现了 BuildContext 接口的 Element。
因为 Widget
是不可变的,当某个 Widget
的配置改变的时候,整个 Widget
树都要被重建。例如当我们改变一个 Container
的颜色的时候,框架就会触发重建整个 Widget
树的动作。因为有了 Element
的存在,Flutter
会对比新的 Widget
树中的第一个 Widget
和 之前的 Widget
。接下来比较 Widget
树中第二个 Widget
和之前的 Widget
,以此类推,直到 Widget
树比较完成,如上面的分析。
class ThreeTree extends StatelessWidget {
Widget build(BuildContext context) {
return Contariner(
color: Colors.yellow,
child: Container(
color: Colors.red
)
)
}
}
Flutter
遵循一个基本原则,判断新的 Widget
和老的 Widget
是否是同一个类型:
如果不是同一个类型,那就把 Widget
、Element
、RenderObject
分别从他们的树(包括他们的子树)上移除,然后重新创建新的对象;
如果是同一个类型,那就仅仅修改 RenderObject
中的配置,然后继续向下遍历;
在示例中,ThreeTree Widget
是和原来一样的类型,他的配置也和原来的 ThreeTreeRender
一样的,所以什么都不会发生。下一个节点在 Wideget
树中是 Container Widget
,它的类型和原来的一样的,但是他的颜色变化了,所以 RenderObject
的配置也会发生对应的变化,然后会重新渲染,其他的对象保持不变。
这个过程非常快,因为 Widget 的不变性和轻量级使得他能够快速创建,这个过程中那些重量级的 RenderObject 则保持不变,直到与其对应类型的 Widget 从Widget树中被移除。
当Widget的类型发生改变时
class ThreeTree extends StatelessWidget {
Widget build(BuildContext context) {
return Contariner(
color: Colors.yellow,
child: ElevatedButton(
child: Text("three tree"),
onPressed: () {},
);
)
}
}
和前面说的流程一样, Flutter
会从新的 Widget
树的顶端向下遍历,与原有树中的 Widget
类型对比。
因为 ElavateButton
的类型与 Element
树中对应位置的 Element
类型不同, Flutter
将会从各自的树上删除这个 Element
和相对应的 ContainerRender
, 然后 Flutter
将会重建 ElevatedButton
相对应的 ``Element和
RenderObject`。
当新的 RenderObject 树被重建后将会计算布局,然后绘制到屏幕上。Flutter 内部使用了很多优化方法和缓存策略来处理,所以我们不需要手动处理这些。