从源码看Flutter三棵树渲染机制与原理
文章目录
- 什么是三棵树
- Widget树
- Elements树
- Render树
- 三棵树如何运行
- 三棵树的作用
- Element
- Element 的生命周期
- initial 初始状态
- active
- inactive
- defunct
- StatelessElement
- StatefulElement
- RenderObjectElement
- RenderObject
- 更新时的三棵树
Flutter
是一个优秀的 UI 框架,借助它开箱即可用的 Widgets
我们能够快速搭建出漂亮和高性能的用户界面。那么那些 Widget
底层又是如何完成渲染的呢?
什么是三棵树
在 Flutter
中和 Widgets
一起协同工作的还有另外两个伙伴: Elements
和 RenderObjects
,由于都有着树形结构,我们常称为三棵树。
Widget树
Widget
是 Flutter
的核心部分,是用户界面的不可变描述,widget
的功能也就是描述一个 UI 元素的配置数据,也就是说 Widget
并不是最终绘制到屏幕上的元素,它只是描述显示元素的一个配置而已。
事实上再代码运行的过程中并没有明确的 widget 树的概念,这棵树是我们在开发过程中对 widget 嵌套的描述,因为确实长得像一棵树所以才这样说。
abstract class Widget {const Widget({ this.keuy });final Key key;@protected/// createElement 是抽象方法,子类必须实现,该方法创建一个 Element,所以每个 Element 都会对应一个 widget 对象Element creatElement();/// 判断 oldWidget 和 newWidget 是不是同一个 widget, 如果 runtimeType 和 key 相同则认为是同一个 widgetstatic bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;}
}
需要注意的是 widget 不能被修改,如果修改只能重新创建,因为 widget 并不参与渲染,它只是一个配置文件而已,只需要告诉渲染层自己的样式即可。
Elements树
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;@overrideWidget get widget => _widget;Widget _widget;RenderObject get renderObject {...}@mustCallSupervoid mount(Element parent, dynamic newSlot){...}@mustCallSupervoid activate(){...}@mustCallSupervoid deactivate(){...}@mustCallSupervoid unmount(){...}
}
Render树
用于应用界面布局和绘制,负责真正的渲染,保存了元素大小,布局信息;另外实例化一个 RenderObject 是非常消耗性能的,下面会有详细的介绍。
三棵树如何运行
简单了解了三棵树之后,那 Flutter
是如何创建和布局的呢?以及三棵树之间他们是如何协同的呢?可以通过一个示例来看:
class ThreeTree extends StatelessWidget {@overrideWidget 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
树.
Element
Element 的生命周期
initial 初始状态
_ElementLifeCycle _lifecycleState = _ElementLifecycle.initial
active
/// RenderObjectElement 的 mount 方法
@override
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
@protectedElement updateChild(Element child, Widget newWidget, dynamic newSlot) {/// 没有新的 widget,并且有原来的widget,则移除原来的 child,因为他不再有配置if(newWidget == null) {if(child != null) {deactivateChild(child);return null;}Element newChild;/// 原来有 childif(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
状态。
@mustCallSuper
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();
}
inactive
从“活动”到“非活动”生命周期状态的转换
在上面updateChild
方法中,我们可以看到新的 widget
为空并且存在旧的,就会调用 deactiveChild
移除 child
, 然后调用 deactivate
方法将_lifecycleState
设置为inactive
.
@mustCallSuper
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;
}
defunct
从“非活动”到“已失效”生命周期状态的转换
@mustCallSuper
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;
}
StatelessElement
Container
创建出来的是 StatelessElement
, 下面就从核心代码看一下它的调用过程。
class StatelessElement extends ComponentElement {/// 通过 createElement 创建传入的 widgetStatelessElement(StatelessWidget widget): super(widget);@overrideStatelessWidget 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;@overridebool get debugDoingBuild => _debugDoingBuild;@overridevoid mount(Element? parent, Object? newSlot) {super.mount(parent, newSlot);_firstBuild();}void _firstBuild() {rebuild();}@overridevoid performRebuild() {//.....}@protectedWidget build();
}abstract class Element extends DiagnosticableTree implements BuildContext {//构造方法,接受一个 widget 参数Element(Widget widget):assert(widget != null),_widget = widget;@overrideWidget 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.@protectedvoid performRebuild();
}
整个过程可以分几步理解
- 这里创建了
StatelessElement
, 创建成功后,frameWork
就会调用mount
方法,因为StatelessElement
没有实现mount
, 所以这里调用的是ComponentElement
的mount
.- 在
mount
中调用了_firstBuild
方法进行第一次构建(注:这里调用的是实现类StatelessElement
的_firstBuild
方法)_firstBuild
方法最后调用的是super._firstBuild
,也就是ComponentElement
的_firstBuild
方法,最终走到了rebuild()
。由于ComponentElement
没有重写,所以最终调用的是Element
的rebuild()
方法。rebuild
最终又会调用到ComponentElement
的PerformRebuild
方法。如下:
@override@pragma('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();}
@pragma('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
方法。大致流程如下:
StatefulElement
相比于StatelessWidget
,StatefulWidget
中多了一个State
,在 StatefulWidget
中通过creatState
来创建一个State
,如下:
abstract class StatefulWidget extend Widget {const StatefulWidget({key? key}) : super{key:key};@overrideStatefulElement creatElement() => StatefulElement(this);@protected@factoryState 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;@overrideWidget build() => state.build(this);
}
在StatefulElement
中通过调用 widget.createState
获取到了state
对象。StatefulElement
创建完成后,framework
就会调用StatelessElement
中的mount
方法了。
和 StatelessElement 不同的是:
StatelessElement
中是通过调用widget.build(this)
方法StatefulElement
中是通过调用State.build(this)
方法
RenderObjectElement
MutiChildRenderObjectElement
是继承自 RenderObjectElement
的。使用情况如SizedBox
,Flex
.
class MultiChildRenderObjectElement extends RenderObjectElement {@overridevoid 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;//遍历所有的childfor (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 {@overridevoid 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
。
RenderObject
上面每提到一个 Element
都对应一个 RenderObject
,我们可以通过 Element.renderObject
来获取。并且 RenderObeject
的主要职责是Layout
,所有 RenderObject
会组成一个渲染树 RenderTree
。
通过上面的分析,可以看到树的核心就是 mount
方法。来看下 RenderObjectElement.mount
方法。
@override
void mount(Element? parent, Object? newSolt) {super.mount(parent, newSlot);_renderObject = widget.creatRenderObject(this);attachRenderObject(newSlot);_dirty = false;
}
在执行完 super.mount
之后(将 parent
插入到 element
树中),执行了 attachRenderObject
方法。
@override
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
@override 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<RenderObject>
, 可以看下ContainerParentDataMixin<RenderObject>
在这个类// 具有子项列表的渲染对象的通用混合 // 为具有双向链接的子项列表的渲染对象子类提供子模型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 childfinal 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 childrenassert(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 {@overrideWidget 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 {@overrideWidget 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 内部使用了很多优化方法和缓存策略来处理,所以我们不需要手动处理这些。
从源码看Flutter三棵树渲染机制与原理相关推荐
- addeventlistener事件参数_从Chrome源码看浏览器的事件机制
在上一篇<从Chrome源码看浏览器如何构建DOM树>介绍了blink如何创建一棵DOM树,在这一篇将介绍事件机制. 上一篇还有一个地方未提及,那就是在构建完DOM之后,浏览器将会触发DO ...
- linux内核第一个函数,通过内核源码看函数调用之前世今生 - 极光 - CSDN博客
通过内核源码看函数调用之前世今生 作者:杨小华 栈(Stack):一个有序的积累或堆积 韦氏词典 对每一位孜孜不倦的程序员来说,栈已深深的烙在其脑海中,甚至已经发生变异.栈可以用来传递函数参数.存储局 ...
- JDK源码分析——Java的SPI机制分析与实战
重点提示:在我博客中的所有的源码分析的实例,我都将会放到github上,感兴趣的朋友可以下载下来调试运行,我相信还是可以有所收获的.我的目的是让所有读到我博客的朋友都可以了解到有价值的东西,学习到ja ...
- codeblock socket 编译错误_从Linux源码看Socket(TCP)Client端的Connect
从Linux源码看Socket(TCP)Client端的Connect 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的 ...
- linux内核线程socket,从Linux源码看Socket(TCP)的accept
从Linux源码看Socket(TCP)的accept 前言 笔者一直以为若是能知道从应用到框架再到操做系统的每一处代码,是一件Exciting的事情. 今天笔者就从Linux源码的角度看下Serve ...
- 从JDK源码看关闭钩子
关闭钩子 Java提供了Shutdown Hook机制,它让我们在程序正常退出或者发生异常时能有机会做一些清场工作.使用的方法也很简单,Java.Runtime.addShutdownHook(Thr ...
- 从 Chrome 源码看浏览器如何计算 CSS
作者:李银城(内容经作者授权转载) 原文链接请点击左下角「阅读原文」 在< Effective 前端 6 :避免页面卡顿>这篇里面介绍了浏览器渲染页面的过程: 并且< 从Chrome ...
- 从源码看ANDROID中SQLITE是怎么通过CURSORWINDOW读DB的
更多内容在这里查看 https://ahangchen.gitbooks.io/windy-afternoon/content/ 执行QUERY 执行SQLiteDatabase类中query系列函 ...
- 小白前端之路:手写一个简单的vue-router这几年,好像过的好快,怀念我的大学生活。 - 连某人 大三实习生,之前写过简单MVVM框架、简单的vuex、但是看了vue-router的源码(看了
这几年,好像过的好快,怀念我的大学生活. 连某人 大三实习生,之前写过简单MVVM框架.简单的vuex.但是看了vue-router的源码(看了大概)之后就没有写,趁着周末不用工作(大三趁着不开学出来 ...
最新文章
- python 开放_Python
- 《Java 核心技术卷1 第10版》学习笔记------异常
- 【算法竞赛学习】气象海洋预测-Task2 数据分析
- @Autowired 与 @Resource的区别
- centos7.5 supervisor +nginx 开机启动设置(实测最有效)以及出现问题思路
- python嵌套循环_关于Python嵌套循环代码优化
- NVM区数据备份机制
- asp.net中使用mschart控件
- 【工作技巧】WinRAR去除广告
- ES2015 解构 Destructuring
- [安卓按键精灵]彩色图片转黑白图(二值化)
- 【转】ESXI 7.0 打包网卡驱动
- MPB:中农冯固组-​利用13C-DNA-SIP法示踪根际和菌丝际活性解磷细菌
- 2021年中式烹调师(中级)考试题库及中式烹调师(中级)报名考试
- linux 摄像头 音频,调用宇视摄像机SDK获取IPC的音视频码流
- 【精品推荐】程序员必定会爱上的十款软件:不用就太浪费了@^@
- 名词从句:主语从句、宾语从句、表语从句、同位语从句
- WINCC使用OPC UA与S7-1200通讯
- hadoop1.1.2分布式安装---集群动态增减节点
- Real-time Multiple People Tracking with Deeply Learned Candidate Selection and Person Re-Identificat
热门文章
- Tanh 激活函数及求导过程
- CUDA error: CUBLAS_STATUS_INVALID_VALUE when calling `cublasSgemm( handle, opa, opb, m, n, k, alpha
- 2023校园二手交易网站的设计与实现|毕业设计(项目资料+运行)
- 交互设计专业书籍推荐(内有部分书籍电子版下载)
- matlab中创建m×n阶矩阵,并用bij表示
- 6人游定增4000万 并完成部分认购,估值4亿元
- 《猫与桃花源》合作阿里渲染云,刷新国产动画电影新高度
- 【Golang】GOOROOT/GOPATH/GOBIN
- grumble.js 气泡形状的提示(Tooltip)控件
- matlab绘制庞加莱截面_matlab庞加莱截面法画Lorenz系统分岔图(附图).doc