我们在学习 Flutter 的时候,可能经常看到三个名词:Widget、RenderObject 和 Element ,弄懂这几个概念可能也是入门 Flutter 框架原理的第一步。
01
—
Widget
在 Flutter 中,万物皆是 Widget,无论是可见的还是功能型的,那么 Widget 究竟是什么呢?
按照惯例,先看官方文档。
-
Widget 的作用是用来保存 Element 的配置信息的。
-
Widget 本身是不可变的。
-
Element 根据 Widget 里面保存的配置信息来管理渲染树。
-
Widget 可以多次的插入到 Widget 树中,每插入一次,Element 都要重新装载一遍 Widget 。
-
Widget 里面的 key 属性用来决定依赖这个 Widget 的 Element 在 Element 树中是更新还是移除。
接下来看一下 Widget 的定义。
abstract class Widget {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
通过上面 Widget 的定义,可以看到有两个重要的方法,一个是通过 createElement 来创建 Element 对象的,一个是根据 key 来决定更新行为的 canUpdate 方法。
以 Opacity 为例,Opacity 做为一个 Widget ,只保存另一个配置信息:opacity,这个属性决定了透明度,范围在 0 到 1 之间。
Opacity 既然做为一个 Widget,肯定是 Widget 的子类,其继承关系如下:
Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget
Opacity 的定义如下:
class Opacity extends SingleChildRenderObjectWidget {
const Opacity({
Key key,
@required this.opacity,
this.alwaysIncludeSemantics = false,
Widget child,
}) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
assert(alwaysIncludeSemantics != null),
super(key: key, child: child);
final double opacity;
@override
RenderOpacity createRenderObject(BuildContext context) {
return RenderOpacity(
opacity: opacity,
alwaysIncludeSemantics: alwaysIncludeSemantics,
);
}
@override
void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
renderObject
..opacity = opacity
..alwaysIncludeSemantics = alwaysIncludeSemantics;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DoubleProperty('opacity', opacity));
properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
}
}
通过上面的代码,可以看到 opacity 是 final 类型的属性,只能做为构造函数参数传递进去,不可改变,因此如果要更新这个属性,必须新建一个 Opcity 对象,这也是为什么我们代码里的 Widget build(BuildContext context) 方法里面每次 build 都会创建新的对象实例的原因。
02
—
RenderObject
-
RenderObject 是做为渲染树中的对象存在的。
-
RenderObject 不会定义约束关系,也就是不会对子控件的布局位置、大小等进行管理。
-
RenderObject 中有一个 parentData 属性,这个属性用来保存其孩子节点的特定信息,如子节点位置,这个属性对其孩子是透明的。
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 是哪里来的呢?是在 Widget 里面创建的。如上面的 Opacity 中重写了 createRenderObject 方法来创建 RenderOpacity 对象。
@override
RenderOpacity createRenderObject(BuildContext context) {
return RenderOpacity(
opacity: opacity,
alwaysIncludeSemantics: alwaysIncludeSemantics,
);
}
在 RenderOpacity 内部实现了布局、点击检测和大小计算等功能。
03
—
Element
-
Element 可以理解为是其关联的 Widget 的实例,并且关联在 Widget 树的特定位置上。
-
由于 Widget 是不可变的,因此一个 Widget 可以同时用来配置多个子 Widget 树,而 Element 就用来代表特定位置的 Widget 。
-
Widget 是不可变的,但是 Element 是可变的。
-
一些 Element 只能有一个子节点,如 Container,Opacity,Center 还有一些可以有多个子节点,如 Column ,Row 和 ListView 等。
Element 的 生命周期:
-
Flutter framework 通过 Widget.createElement 来创建一个 element 。
-
每当 Widget 创建并插入到 Widget 树中时,framework 就会通过 mount 方法来把这个 widget 创建并关联的 element 插入到 element 树中(其父 element 会给出一个位置)。
-
通过 attachRenderObject 方法来将 render objects 来关联到 render 树上,这时可以认为这个 widget 已经显示在屏幕上了。
-
每当执行了 rebuid 方法,widget 代表的配置信息改变时(创建了一个新的 widget),framewrok 就会调用这个新的 widget 的 update 方法(新的 widget 的 和老的 widget 有相同的 runtimeType 和 key,如果不同,就要先 unmounting 然后重新装载 widget)。
-
当 element 的祖先想要移除一个子 element 时,可以通过 deactivateChild 方法,先把这个 element 从 树中移除,然后将这个 element 加入到一个“不活跃元素列表”中,接着 framework 就会将这个 element 从屏幕移除(当下一个渲染帧到来这个 element 依然不活跃)。
由于 是在 Widget 中创建了Element,类似于 Widget 的继承关系,Element 的继承关系如下:
SingleChildRenderObjectElement → RenderObjectElement → Element
接着看一下 Opacity 中,如何创建一个 Element 。Opacity 继承自 SingleChildRenderObjectElement,在 SingleChildRenderObject 中创建了 Element
@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);
在 RenderObjectElement 中提供了 mount 方法
abstract class RenderObjectElement extends Element {
RenderObjectElement(RenderObjectWidget widget) : super(widget);
RenderObject _renderObject;
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
}
通过上面的代码,我们能够发现,Element 中通过 widget.createRenderObject 方法也拿到了 RenderObject 对象,因此 Element 其实是同时包含 RenderObject 和 Widget 。
mount 方法会将 element 插入到 element 树中,mount 中还会调用 attachRenderObject 方法。
abstract class RenderObjectElement extends Element {
@override
void attachRenderObject(dynamic newSlot) {
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
}
在这个方法里,通过 _findAncestorRenderObjectElement 方法, 找到了Element树上的祖先Element,如果祖先不为空,就调用insertChildRenderObject方法,这个方法的意思就是把renderObject的child替换成newSlot,然后通过 _updateParentData 用于更新布局数据的一些信息。
04
—
总结
上面只是简单介绍了一下 Flutter 中的 Widget 、RenderObject 和 Element 中的概念,而 Widget,Element和RenderObject体系是Flutter框架的核心 至于内部原理以及如果工作的,需要结合 Flutter 框架结构运行原理来看,这样才能更好的理解这些概念。
推荐阅读