H5W3
当前位置:H5W3 > 其他技术问题 > 正文

Widget、RenderObject 与 Element

Widget、RenderObject 与 Element

我们在学习 Flutter 的时候,可能经常看到三个名词: Widget、RenderObject 和 Element ,弄懂这几个概念可能也是入门 Flutter 框架原理的第一步。

一、 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 都会创建新的对象实例的原因。

二、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 内部实现了布局、点击检测和大小计算等功能。

三、 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 用于更新布局数据的一些信息。

总结

上面只是简单介绍了一下 Flutter 中的 Widget 、RenderObject 和 Element 中的概念,而 Widget,Element和RenderObject体系是Flutter框架的核心 至于内部原理以及如果工作的,需要结合 Flutter 框架结构运行原理来看,这样才能更好的理解这些概念。

参考:

Flutter的原理及美团的实践

Flutter, what are Widgets, RenderObjects and Elements

深入了解Flutter界面开发

欢迎关注「Flutter 编程开发」微信公众号 。

本文地址:H5W3 » Widget、RenderObject 与 Element

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址