注意:为了让分析更加简单,和逻辑清晰,我们去掉了部分源码和注释,只留下了主要的代码和逻辑。如果没有看过上一篇文章,请点击下面的链接。 Flutter必须理解Widget、Element、RenderObject的关系(一)
Element概述
updateChild方法
接着上篇接着来,上面提到过这个这个方法比较重要,我们将单独拿一个章节讲解,下面是updateChild()源码。
abstract class Element extends DiagnosticableTree implements BuildContext {
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);//注释1
return null;
}
if (child != null) {
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);//注释2
return child;
}
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);//注释3
return child;
}
deactivateChild(child);
}
return inflateWidget(newWidget, newSlot);//注释4
}
}
这个方法的意思就更新Element配置的一个函数,具体是怎么更新的呢,其实还是比较简单的,在前面的的文章中我们说过关于Element树的概念,这个函数就是从树中移除相关的数据。
-
注释1
deactivateChild()是把Element从Element树上删除,
分析到这里我们先告一个小段落,现在终结一下Element。
RenderObject概述
RenderObject定义
还是先从概念上入手,下面是ReaderObject的概念。
An object in the render tree.
这个概念很简单,大概的意思是渲染树上的一个对象。从概念上得出每个RenderObject都挂在一个渲染树上,我们看一下RenderObject的源码,下面是RenderObject的源码。
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
ParentData parentData;
Constraints _constraints;
void layout(Constraints constraints, { bool parentUsesSize = false }) {
}
void paint(PaintingContext context, Offset offset) { }
void performLayout();
void markNeedsPaint() {
}
}
我们共提供这些方法可用看出来,RenderObject的主要职责是绘制和布局,也就是我们说的,这是一个真正的渲染对象,我们下面分析一下这个对象是怎么布局和渲染的。
如果我们想在屏幕上想画一个红色的正方形,两个重要的问题需要解决,第一是,画在哪里?第二是,是怎么画?画在哪里就是布局,怎么画就是渲染,下面我们对着两个问题进行分析,先从布局开始。
RenderObject布局
我们先从layout入手,这个方法是计算渲染对象的大小和布局,这方法通常不要被在类覆盖,如果你想重写布局,要重写performLayout()。
abstract class RenderObject {
void layout(Constraints constraints, { bool parentUsesSize = false }) {
RenderObject relayoutBoundary;//注释1
if (!parentUsesSize || sizedByParent || constraints.isTight
|| parent is! RenderObject) {//注释2
relayoutBoundary = this;//注释3
} else {
final RenderObject parent = this.parent;
relayoutBoundary = parent._relayoutBoundary;//注释4
}
_relayoutBoundary = relayoutBoundary;
if (sizedByParent) {
performResize();//注释5
}
performLayout();//注释6
markNeedsPaint();
}
}
上面就是layout方法,这方法有两个参数,第一个参数constraints,是通过父类传入的,也就是大家常说的约束从上到下。
-
注释1
relayoutBoundary这变量比较重要,我们打算用用个小结讲解。
RelayoutBoundary
在注释1处声明了一个属性relayoutBoundary,属性叫做布局边界,这个这属性是提高渲染效率的(因为在布局和渲染的都会用到这属性),我们知道RenderObject在渲染树中,现在如果一个叶子RenderObject对象发生布局变化,那么一定会导致这个叶子节点的父布局从新布局,这必定导致低效,那么Flutter就用这个属性防止父节点从新布局,但是这个需要满足几个条。
-
parentUsesSize
layout的第二个参数,父控件的布局是依赖子控件的布局,默认值是false,也就是默认父控件不依赖子控件布局。
-
sizedByParent
子控件的大小完全在父控件的约束条件下,也就是子控件在父控件的min和max之间。
-
constraints.isTight
就是min等于max,这比较容易理解。
-
parent is! RenderObject
这个条件很容易理解了,就是parent不是RenderObject。
上面的4个条件,如果一个成立就执行注释3的代码,注释3的意思布局边界是自己,因为这个节点的布局变化不会引起父节点的从新布局。否则执行注释4,把父节点的布局边界赋值给自己。
- 注释5
performResize()方法子类实现的方法,这个方法主要的功能是更新渲染对象的大小,当然这个这个方法被调用的条件是sizedByParent是true,也就是说子控件的大小完全在父控件的约束条件下,执行这个方法。
- 注释6
performLayout()方法同样的是需要子类去实现的,计算RenderObject的布局。
下面我们分析一下RenderConstrainedBox的performLayout方法,还是先看一下RenderConstrainedBox的继承关系。
class RenderConstrainedBox extends RenderProxyBox {
}
class RenderProxyBox extends RenderBox {
}
abstract class RenderBox extends RenderObject {
}
从上面的代码中很明显能看出来RenderConstrainedBox最终继承RenderObject,我们下面就看一下performLayout()方法的具体实现。
class RenderConstrainedBox extends RenderProxyBox {
@override
void performLayout() {
if (child != null) {
child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true)
size = child.size;
} else {
size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
}
}
}
这个方法能看出来,当调用RenderObject的layout()方法的时候,会调用RenderConstrainedBox的performLayout(),这个方法又会调用child.layout()方法,直到布局完成。约束是从父类传到子类的,但是size是从子类给到父类的,用一张图表示就清楚多了,这个就是大家常说的,constraints从上到下,size从下到上。
RenderObject渲染
在RenderObject中主要渲染的的函数是paint(),在RenderObject中没有具体的实现,需要子类自己实现,下面是paint方法。
void pait(PaintingContext context, Offset offset) { }
上面的paint有两个参数,先解释两个参数的意思,让后自然就能明白这个函数的意思。
-
context
Context是PaintingContext,这类继承自ClipContext,在ClipContext中有一个Canvas,这样好像就理解了,原来context是用来画东西的画布。
-
offset
offset是Offset,就是屏幕坐标上的一个位置。
这样我们应该能猜到这个paint()就是一个,将一个context,画到一个屏幕位置上,这里大家可能有一个疑问,比如我们想画一个红色的正方形,现在只有画布和画到那个位置,还没有告诉我红色方方块的大小呢,怎么画?在上一小节中(RenderObject布局),我们讲了performResize()这个函数,这个函数已经计算出红色方块的大小了。