WidgetTree:存放渲染内容、它只是一个配置数据结构,创建是非常轻量的,在页面刷新的过程中随时会重建
Element 是分离 WidgetTree 和真正的渲染对象的中间层, WidgetTree 用来描述对应的Element 属性,同时持有Widget和RenderObject,存放上下文信息,通过它来遍历视图树,支撑UI结构。
RenderObject (渲染树)用于应用界面的布局和绘制,负责真正的渲染,保存了元素的大小,布局等信息,实例化一个 RenderObject 是非常耗能的
当应用启动时 Flutter 会遍历并创建所有的 Widget 形成 Widget Tree,通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element Tree。最后调用 Element 的 createRenderObject() 方法创建每个渲染对象,形成一个 Render Tree。
那么,flutter为什么要设计成这样呢?为什么要弄成复杂的三层结构?
答案是性能优化。如果每一点细微的操作就去完全重绘一遍UI,将带来极大的性能开销。flutter的三棵树型模式设计可以有效地带来性能提升。
widget的重建开销非常小,所以可以随意的重建,因为它不一会导致页面重绘,并且它也不一定会常常变化。 而renderObject如果频繁创建和销毁成本就很高了,对性能的影响比较大,因此它会缓存所有页面元素,只是当这些元素有变化时才去重绘页面。
而判断页面有无变化就依靠element了,每次widget变化时element会比较前后两个widget,只有当某一个位置的Widget和新Widget不一致,才会重新创建Element和widget;其他时候则只会修改renderObject的配置而不会进行耗费性能的RenderObject的实例化工作了。
Widget的渲染原理
所有的Widget都会创建一个Element对象
并不是所有的Widget都会被独立渲染!只有继承RenderObjectWidget的才会创建RenderObject对象!(Container就不会创建RenderObject、column和padding这些可以创建RenderObject)
在Flutter渲染的流程中,有三颗重要的树!Flutter引擎是针对Render树进行渲染!
Widget树、Element树、Render树
每一个Widget都会创建一个Element对象
隐式调用createElement方法。Element加入Element树中,它会创建RenderElement、ComponentElement(又分为StatefulElement和StatelessElement)。
RenderElement主要是创建RenderObject对象, 继承RenderObjectWidget的Widget会创建RenderElement
StatefulElement继承ComponentElement,StatefulWidget会创建StatefulElement
StatelessElement继承ComponentElement,StatelessWidget会创建StatelessElement
-主要就是调用build方法 并且将自己(Element)传出去
Widget从功能上看,可以分为三大类:
组合类Widget。这类Widget主要用来组合其他更基础的Widget,得到功能更加复杂的Widget。平常的业务开发一般用的就是此类Widget。
渲染类Widget,这类Widget是框架最核心的Widget,会参与后面的布局和渲染流程;只有这种类型的Widget会绘制到屏幕上。
代理类Widget,其本身并不涉及Widget内部逻辑,只是为子Widget提供一些附加的中间功能。例如:InheritedWidget用于将一些状态信息传递给子孙Widget。
Element从功能上看,可以分为两大类:
组合类Element。这类Element主要用来组合其他更基础的Element,得到功能更加复杂的Element。开发时常用到的StatelessWidget和StatefulWidget相对应的Element:StatelessElement和StatefulElement,即属于ComponentElement。
渲染类Element,对应Renderer Widget,是框架最核心的Element。RenderObjectElement主要包括LeafRenderObjectElement,SingleChildRenderObjectElement,和MultiChildRenderObjectElement。其中,LeafRenderObjectElement对应的Widget是LeafRenderObjectWidget,没有子节点;SingleChildRenderObjectElement对应的Widget是SingleChildRenderObjectWidget,有一个子节点;MultiChildRenderObjectElement对应的Widget是MultiChildRenderObjecWidget,有多个子节点。
2.1 ComponentElement
2.1.1 与核心元素关系
如上文所述,ComponentElement分为StatelessElement和StatefulElement,这两种Element同核心元素Widget以及State之间的关系如下图所示。
如图:
2.1.2 ComponentElement核心流程
一个Element的核心操作流程有,创建、更新、销毁三种,下面将分别介绍这三个流程。
ComponentElement的创建起源与父Widget调用inflateWidget,然后通过mount将该Element挂载至Element Tree,并递归创建子节点。
由父Element执行更新子节点的操作(updateChild),由于新旧Widget的类型和Key均未发生变化,因此触发了Element的更新操作,并通过performRebuild将更新操作传递下去。其核心函数updateChild之后会详细介绍。
由父Element或更上级的节点执行更新子节点的操作(updateChild),由于新旧Widget的类型或者Key发生变化,或者新Widget被移除,因此导致该Element被转为未激活状态,并被加入未激活列表,并在下一帧被失效。
2.1.3 ComponentElement核心函数
下面对ComponentElement中的核心方法进行介绍。
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key key = newWidget.key;
//复用GlobalKey对应的Element
if (key is GlobalKey) {
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild;
}
}
//创建Element,并挂载至Element Tree
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
inflateWidget的主要职责如下:
void mount(Element parent, dynamic newSlot) {
//更新_parent等属性,将元素加入Element Tree
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner;
//注册GlobalKey
final Key key = widget.key;
if (key is GlobalKey) {
key._register(this);
}
_updateInheritance();
}
当Element第一次被插入Element Tree的时候,该方法被调用。其主要职责如下:
@override
void performRebuild() {
//调用build函数,生成子Widget
Widget built;
built = build();
//根据新的子Widget更新子Element
_child = updateChild(_child, built, slot);
}
performRebuild的主要职责如下:
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
此函数主要职责为:
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
//新的Child Widget为null,则返回null;如果旧Child Widget,使其未激活
if (child != null)
deactivateChild(child);
return null;
}
Element newChild;
if (child != null) {
//新的Child Widget不为null,旧的Child Widget也不为null
bool hasSameSuperclass = true;
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)){
//Key和RuntimeType相同,使用update更新
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
//Key或RuntimeType不相同,使旧的Child Widget未激活,并对新的Child Widget使用inflateWidget
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
//新的Child Widget不为null,旧的Child Widget为null,对新的Child Widget使用inflateWidget
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
该方法的主要职责为:
根据新的子Widget,更新旧的子Element,或者得到新的子Element。其核心逻辑可以用表格表示:
newWidget == null | newWidget != null | |
---|---|---|
Child == null | 返回null | 返回新Element |
Child != null | 移除旧的子Element,返回null | 如果Widget能更新,更新旧的子Element,并返回之;否则创建新的子Element并返回。 |
该逻辑概括如下:
2.2 RenderObjectElement
2.2.1 RenderObjectElement与核心元素关系
RenderObjectElement同核心元素Widget及RenderObject之间的关系如下图所示:
如图:
2.2.2 RenderObjectElement核心流程
如ComponentElement一样,RenderObjectElement的核心操作流程有,创建、更新、销毁三种,接下来会详细介绍这三种流程。
RenderObjectElement的创建流程和ComponentElement的创建流程基本一致,其最大的区别是ComponentElement在mount后,会调用build来创建子Widget,而RenderObjectElement则是create和attach其RenderObject。
RenderObjectElement的更新流程和ComponentElement的更新流程也基本一致,其最大的区别是ComponentElement的update函数会调用build函数,重新触发子Widget的构建,而RenderObjectElement则是调用updateRenderObject对绑定的RenderObject进行更新。
RenderObjectElement的销毁流程和ComponentElement的销毁流程也基本一致。也是由父Element或更上级的节点执行更新子节点的操作(updateChild),导致该Element被停用,并被加入未激活列表,并在下一帧被失效。其不一样的地方是在unmount Element的时候,会调用didUnmountRenderObject失效对应的RenderObject。
2.2.3 RenderObjectElement核心函数
下面对RenderObjectElement中的核心方法进行介绍。
该函数和ComponentElement的inflateWidget函数完全一致,此处不再复述。
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
该函数的调用时机和ComponentElement的一致,当Element第一次被插入Element Tree的时候,该方法被调用。其主要职责也和ComponentElement的一致,此处只列举不一样的职责,职责如下:
@override
void performRebuild() {
//更新renderObject
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
performRebuild的主要职责如下:
调用updateRenderObject更新对应的RenderObject。
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
update的主要职责如下:
该函数和ComponentElement的inflateWidget函数完全一致,此处不再复述。
@protected
List updateChildren(List oldChildren, List newWidgets, { Set forgottenChildren }) {
int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newWidgets.length - 1;
int oldChildrenBottom = oldChildren.length - 1;
final List newChildren = oldChildren.length == newWidgets.length ?
oldChildren : List(newWidgets.length);
Element previousChild;
// 从顶部向下更新子Element
// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
final Widget newWidget = newWidgets[newChildrenTop];
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
final Element newChild = updateChild(oldChild, newWidget, IndexedSlot(newChildrenTop, previousChild));
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
// 从底部向上扫描子Element
// Scan the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
final Widget newWidget = newWidgets[newChildrenBottom];
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
oldChildrenBottom -= 1;
newChildrenBottom -= 1;
}
// 扫描旧的子Element列表里面中间的子Element,保存Widget有Key的Element到oldKeyChildren,其他的失效
// Scan the old children in the middle of the list.
final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
Map oldKeyedChildren;
if (haveOldChildren) {
oldKeyedChildren = {};
while (oldChildrenTop <= oldChildrenBottom) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
if (oldChild != null) {
if (oldChild.widget.key != null)
oldKeyedChildren[oldChild.widget.key] = oldChild;
else
deactivateChild(oldChild);
}
oldChildrenTop += 1;
}
}
// 根据Widget的Key更新oldKeyChildren中的Element。
// Update the middle of the list.
while (newChildrenTop <= newChildrenBottom) {
Element oldChild;
final Widget newWidget = newWidgets[newChildrenTop];
if (haveOldChildren) {
final Key key = newWidget.key;
if (key != null) {
oldChild = oldKeyedChildren[key];
if (oldChild != null) {
if (Widget.canUpdate(oldChild.widget, newWidget)) {
// we found a match!
// remove it from oldKeyedChildren so we don't unsync it later
oldKeyedChildren.remove(key);
} else {
// Not a match, let's pretend we didn't see it for now.
oldChild = null;
}
}
}
}
final Element newChild = updateChild(oldChild, newWidget, IndexedSlot(newChildrenTop, previousChild));
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
}
newChildrenBottom = newWidgets.length - 1;
oldChildrenBottom = oldChildren.length - 1;
// 从下到上更新底部的Element。.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = oldChildren[oldChildrenTop];
final Widget newWidget = newWidgets[newChildrenTop];
final Element newChild = updateChild(oldChild, newWidget, IndexedSlot(newChildrenTop, previousChild));
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
// 清除旧子Element列表中其他所有剩余Element
// Clean up any of the remaining middle nodes from the old list.
if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
for (final Element oldChild in oldKeyedChildren.values) {
if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
deactivateChild(oldChild);
}
}
return newChildren;
}
该函数的主要职责如下:
其步骤如下:
2.1.3 Element小结
本文主要介绍了Element相关知识,重点介绍了其分类,生命周期,和核心函数。重点如下:
通过上篇文章介绍的Element Tree,Flutter Framework会生成一棵RenderObject Tree. 其主要功能如下:
RenderObject Tree是底层的布局和绘制系统。大多数Flutter开发者并不需要直接和RenderObject Tree交互,而是使用Widget,然后Flutter Framework会自动构建RenderObject Tree。
3.1 RenderObject分类
如上图所示,RenderObject主要分为四类:
RenderView是整个RenderObject Tree的根节点,代表了整个输出界面。
RenderAbstractViewport是一类接口,此类接口为只展示其部分内容的RenderObject设计。
RenderSliver是所有实现了滑动效果的RenderObject基类,其常用子类有RenderSliverSingleBoxAdapter等。
RenderBox是一个采用2D笛卡尔坐标系的RenderObject的基类,一般的RenderOBject都是继承自RenderBox,例如RenderStack等,它也是一般自定义RenderObject的基类。
3.2 RenderObject核心流程
RenderObject主要负责布局,绘制,及命中测试,下面会对这几个核心流程分别进行讲解。
布局对应的函数是layout,该函数主要作用是通过上级节点传过来的Constraints和parentUsesSize等控制参数,对本节点和其子节点进行布局。Constraints是对于节点布局的约束,其原则是,Constraints向下,Sizes向上,父节点设置本节点的位置。即:
在接下来的文章中,我们将对该流程进行详细介绍,当前我们只需要记住该原则。
当本节点的布局依赖于其子节点的布局时,parentUsesSize的值是true,此时,子节点被标记为需要布局时,本节点也将被标记为需要布局。这样当下一帧绘制时本节点和子节点都将被重新布局。反之,如果parentUsesSize的值是false,子节点被重新布局时不需要通知本节点。
RenderObject的子类不应该直接重写RenderObject的layout函数,而是重写performResize和performLayout函数,这两个函数才是真正负责具体布局的函数。
RenderObject中layout函数的源码如下:
void layout(Constraints constraints, { bool parentUsesSize = false }) {
//1. 根据relayoutBoundary判断是否需要重新布局
RenderObject relayoutBoundary;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
}
if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
return;
}
_constraints = constraints;
//2. 更新子节点的relayout boundary
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
// The local relayout boundary has changed, must notify children in case
// they also need updating. Otherwise, they will be confused about what
// their actual relayout boundary is later.
visitChildren(_cleanChildRelayoutBoundary);
}
_relayoutBoundary = relayoutBoundary;
//3. 重新计算大小,重新布局
if (sizedByParent) {
try {
performResize();
} catch (e, stack) {
_debugReportException('performResize', e, stack);
}
}
try {
performLayout();
markNeedsSemanticsUpdate();
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
_needsLayout = false;
markNeedsPaint();
}
从源码可以看到,relayoutBoundary是layout函数中一个重要参数。当一个组件的大小被改变时,其parent的大小可能也会被影响,因此需要通知其父节点。如果这样迭代上去,需要通知整棵RenderObject Tree重新布局,必然会影响布局效率。因此,Flutter通过relayoutBoundary将RenderObject Tree分段,如果遇到了relayoutBoundary,则不去通知其父节点重新布局,因为其大小不会影响父节点的大小。这样就只需要对RenderObject Tree中的一段重新布局,提高了布局效率。关于relayoutBoundary将在之后的文章中详细讲解,目前只需要了解relayoutBoundary会将RenderObject Tree分段,提高布局效率。
绘制对应的函数是paint,其主要作用是将本RenderObject和子RenderObject绘制在Canvas上。RenderObject的子类应该重写这个函数,在该函数中添加绘制的逻辑。
RenderObject的子类RenderFlex的paint函数源码如下:
void paint(PaintingContext context, Offset offset) {
//1. 未溢出,直接绘制
if (!_hasOverflow) {
defaultPaint(context, offset);
return;
}
//2. 空的,不需要绘制
// There's no point in drawing the children if we're empty.
if (size.isEmpty)
return;
//3. 根据clipBehavior判断是否需要对溢出边界部分进行裁剪
if (clipBehavior == Clip.none) {
defaultPaint(context, offset);
} else {
// We have overflow and the clipBehavior isn't none. Clip it.
context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint, clipBehavior: clipBehavior);
}
//4. 绘制溢出错误提示
assert(() {
// Only set this if it's null to save work. It gets reset to null if the
// _direction changes.
final List debugOverflowHints = [
ErrorDescription(
'The overflowing $runtimeType has an orientation of $_direction.'
),
ErrorDescription(
'The edge of the $runtimeType that is overflowing has been marked '
'in the rendering with a yellow and black striped pattern. This is '
'usually caused by the contents being too big for the $runtimeType.'
),
ErrorHint(
'Consider applying a flex factor (e.g. using an Expanded widget) to '
'force the children of the $runtimeType to fit within the available '
'space instead of being sized to their natural size.'
),
ErrorHint(
'This is considered an error condition because it indicates that there '
'is content that cannot be seen. If the content is legitimately bigger '
'than the available space, consider clipping it with a ClipRect widget '
'before putting it in the flex, or using a scrollable container rather '
'than a Flex, like a ListView.'
),
];
// Simulate a child rect that overflows by the right amount. This child
// rect is never used for drawing, just for determining the overflow
// location and amount.
Rect overflowChildRect;
switch (_direction) {
case Axis.horizontal:
overflowChildRect = Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0);
break;
case Axis.vertical:
overflowChildRect = Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow);
break;
}
paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints);
return true;
}());
}
这部分代码逻辑为,先判断是否溢出,没有溢出则调用defaultPaint完成绘制,再看是否为空,size是空的话直接返回,最后绘制溢出信息。
其中defaultPaint的源码如下:
void defaultPaint(PaintingContext context, Offset offset) {
ChildType child = firstChild;
while (child != null) {
final ParentDataType childParentData = child.parentData as ParentDataType;
context.paintChild(child, childParentData.offset + offset);
child = childParentData.nextSibling;
}
}
可见defaultPaint会调用paintChild绘制子节点,而如果子节点还有子节点,则paintChild最终又会调用到其paint然后调用到defaultPaint,从而形成循环递归调用,绘制整棵RenderObject Tree。
命中测试是为了判断某个组件是否需要响应一个点击事件,其入口是RenderObject Tree的根节点RenderView的hitTest函数。下面是该函数的源码:
bool hitTest(HitTestResult result, { Offset position }) {
if (child != null)
child.hitTest(BoxHitTestResult.wrap(result), position: position);
result.add(HitTestEntry(this));
return true;
}
从RenderView的构造函数可以看出,child是RenderBox类,因此我们再看RenderBox的hitTest函数。
bool hitTest(BoxHitTestResult result, { @required Offset position }) {
if (_size.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
代码逻辑很简单,如果点击事件位置处于RenderObject之内,如果在其内,并且hitTestSelf或者hitTestChildren返回true,则表示该RenderObject通过了命中测试,需要响应事件,此时需要将被点击的RenderObject加入BoxHitTestResult列表,同时点击事件不再向下传递。否则认为没有通过命中测试,事件继续向下传递。其中,hitTestSelf函数表示本节点是否通过命中测试,hitTestChildren表示子节点是否通过命中测试。
3.3 RenderObject核心函数
RenderObject的核心函数有很多,难以一一列举,在核心流程中已经详细讲解了RenderObject三个核心函数。为了便于理解各个核心函数的作用,这里将RenderObject的核心函数和Android View的核心函数进行比较。以下是比较的表格。
作用 | Flutter RenderObject | Android View |
---|---|---|
绘制 | paint() | draw()/onDraw() |
布局 | performLayout()/layout() | measure()/onMeasure(), layout()/onLayout() |
布局约束 | Constraints | MeasureSpec |
布局协议1 | performLayout() 的 Constraints 参数表示父节点对子节点的布局限制 | measure() 的两个参数表示父节点对子节点的布局限制 |
布局协议2 | performLayout() 应调用各子节点的 layout() | onLayout() 应调用各子节点的 layout() |
布局参数 | parentData | mLayoutParams |
请求布局 | markNeedsLayout() | requestLayout() |
请求绘制 | markNeedsPaint() | invalidate() |
添加 child | adoptChild() | addView() |
移除 child | dropChild() | removeView() |
关联到窗口/树 | attach() | onAttachedToWindow() |
从窗口/树取消关联 | detach() | onDetachedFromWindow() |
获取 parent | parent | getParent() |
触摸事件 | hitTest() | onTouch() |
用户输入事件 | handleEvent() | onKey() |
旋转事件 | rotate() | onConfigurationChanged() |
可见,RenderObject和Android View有很多函数是对应起来的,RenderObject相对于将Android View中的布局渲染等功能单独拆了出来,简化了View的逻辑。
3.4. 小结
本文主要介绍了RenderObject相关知识,重点介绍了其分类,核心流程,和核心函数。重点如下:
系统启动时,runApp方法会被调用,flutter会从最外层的widget去遍历创建一颗widget树;每一个widget创建后会调用createElement()创建相应的element,形成一颗element树;element创建后会通过createRenderObject()创建相应的renderObject树,如此就形成了三棵树。
在渲染树种完成布局排列和绘制。最后合并层级,通过Skia引擎渲染为GPU数据,然后GPU接着将数据交给显示器显示。
而渲染对象树在Flutter的展示过程分为三个阶段:布局、绘制、合成和渲染。
布局:
Flutter采用深度优先机制遍历渲染对象树,决定渲染对象树中各渲染对象在屏幕上的位置和尺寸。在布局过程中,渲染对象树中的每个渲染对象都会接收父对象的布局约束参数,决定自己的大小,然后父对象按照控件逻辑决定各个子对象的位置,完成布局过程。
绘制:
布局完成后,渲染对象树中的每个节点都有了明确的尺寸和位置。Flutter会把所有的渲染对象绘制到不同的图层上。与布局过程一样,绘制过程也是深度优先遍历,而且总是先绘制自身,再绘制子节点。
图层合成:
终端设备的页面越来越复杂,因此Flutter的渲染树层级通常很多,直接交付给渲染引擎进行多图层渲染,可能会出现大量渲染内容的重复绘制,所以还需要先进行一次图层合成,即将所有的图层根据大小、层级、透明度等规则计算出最终的显示效果,将相同的图层归类合并,简化渲染树,提高渲染效率。
合并完成后,Flutter会将几何图层数据交由Skia引擎加工成二维图像数据,最终交由GPU进行渲染,完成界面的展示。
上面已经介绍了三棵树的运作流程,这部分为参考内容(视频教程里边学的)
我们这里以Padding为例,Padding用来设置内边距
Padding是一个Widget,并且继承自SingleChildRenderObjectWidget
继承关系如下:
Padding -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget
我们之前在创建Widget时,经常使用StatelessWidget和StatefulWidget,这种Widget只是将其他的Widget在build方法中组装起来,并不是一个真正可以渲染的Widget(在之前的课程中其实有提到)。
在Padding的类中,我们找不到任何和渲染相关的代码,这是因为Padding仅仅作为一个配置信息,这个配置信息会随着我们设置的属性不同,频繁的销毁和创建。
问题:频繁的销毁和创建会不会影响Flutter的性能呢?
那么真正的渲染相关的代码在哪里执行呢?
我们来看Padding里面的代码,有一个非常重要的方法:
@override
RenderPadding createRenderObject(BuildContext context) {
return RenderPadding(
padding: padding,
textDirection: Directionality.of(context),
);
}
上面的代码创建了什么呢?RenderPadding
RenderPadding的继承关系是什么呢?
RenderPadding -> RenderShiftedBox -> RenderBox -> RenderObject
我们来具体查看一下RenderPadding的源代码:
set padding(EdgeInsetsGeometry value) {
assert(value != null);
assert(value.isNonNegative);
if (_padding == value)
return;
_padding = value;
_markNeedResolution();
}
我们来思考一个问题:
Element什么时候创建?
在每一次创建Widget的时候,会创建一个对应的Element,然后将该元素插入树中。
在SingleChildRenderObjectWidget中,我们可以找到如下代码:
@override
SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
在创建完一个Element之后,Framework会调用mount方法来将Element插入到树中具体的位置:
mount方法
在调用mount方法时,会同时使用Widget来创建RenderObject,并且保持对RenderObject的引用:
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
assert(() {
_debugUpdateRenderObjectOwner();
return true;
}());
assert(_slot == newSlot);
attachRenderObject(newSlot);
_dirty = false;
}
但是,如果你去看类似于Text这种组合类的Widget,它也会执行mount方法,但是mount方法中并没有调用createRenderObject这样的方法。
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_active);
_firstBuild();
assert(_child != null);
}
void _firstBuild() {
rebuild();
}
如果是一个StatefulWidget,则创建出来的是一个StatefulElement
我们来看一下StatefulElement的构造器:
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
....省略代码
_state._widget = widget;
而调用build的时候,本质上调用的是_state中的build方法:
Widget build() => state.build(this);
在StatelessElement中,我们发现是将this传入,所以本质上BuildContext就是当前的Element
Widget build() => widget.build(this);
我们来看一下继承关系图:
abstract class Element extends DiagnosticableTree implements BuildContext
在StatefulElement中,build方法也是类似,调用state的build方式时,传入的是this
Widget build() => state.build(this);
Widget只是描述了配置信息:
Element是真正保存树结构的对象:
RenderObject是真正渲染的对象:
markNeedsLayout
performLayout
markNeedsPaint
paint
等方法