我们在学习 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 框架结构运行原理来看,这样才能更好的理解这些概念。
推荐阅读
深入理解 Flutter 多线程
Flutter 内部工作原理