image.png

图 1 Flutter 架构图

Widget 是 Flutter 功能的抽象描述,是视图的配置信息,同样也是数据的映射,是 Flutter 开发框架中最基本的概念。前端框架中常见的名词,比如视图(View)、视图控制器(View Controller)、活动(Activity)、应用(Application)、布局(Layout)等,在 Flutter 中都是 Widget。

Widget 渲染过程

视图树(View Tree). Flutter 将视图树的概念进行了扩展,把视图数据的组织和渲染抽象为三部分,即 Widget,Element 和 RenderObject。

image.png

图 2 Widget,Element 与 RenderObject

Widget

  • Widget 是 Flutter 世界里对视图的一种结构化描述
  • Widget 是控件实现的基本逻辑单位
  • 里面存储的是有关视图渲染的配置信息
    • 布局
    • 渲染属性
    • 事件响应信息等

Flutter 将 Widget 设计成不可变的,所以当视图渲染的配置信息发生变化时,Flutter 会选择重建 Widget 树的方式进行数据更新,以数据驱动 UI 构建的方式简单高效。

  • Widget 本身并不涉及实际渲染位图,所以它只是一份轻量级的数据结构,重建的成本很低。
  • 由于 Widget 的不可变性,可以以较低成本进行渲染节点复用,因此在一个真实的渲染树中可能存在不同的 Widget 对应同一个渲染节点的情况,这无疑又降低了重建 UI 的成本。

Element

Element 是 Widget 的一个实例化对象,它承载了视图构建的上下文数据,是连接结构化的配置信息到完成最终渲染的桥梁。

Flutter 渲染过程,可以分为这么三步:

  1. 首先,通过 Widget 树生成对应的 Element 树;
  2. 然后,创建相应的 RenderObject 并关联到 Element.renderObject 属性上;
  3. 最后,构建成 RenderObject 树,以完成最终的渲染。

可以看到,Element 同时持有 Widget 和 RenderObject。而无论是 Widget 还是 Element,其实都不负责最后的渲染,只负责发号施令,真正去干活儿的只有 RenderObject。那你可能会问,既然都是发号施令,那为什么需要增加中间的这层 Element 树呢?直接由 Widget 命令 RenderObject 去干活儿不好吗?

  • 答案是,可以,但这样做会极大地增加渲染带来的性能损耗。

因为 Widget 具有不可变性,但 Element 却是可变的。实际上,Element 树这一层将 Widget 树的变化(类似 React 虚拟 DOM diff)做了抽象,可以只将真正需要修改的部分同步到真实的 RenderObject 树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。

RenderObject

RenderObject 是主要负责实现视图渲染的对象。

渲染对象树在 Flutter 的展示过程分为四个阶段,即布局、绘制、合成和渲染。

RenderObjectWidget 介绍

在 Flutter 中,布局和绘制工作实际上是在 Widget 的另一个子类 RenderObjectWidget 内完成的。

我们再来看一下 RenderObjectWidget 的源码,来看看如何使用 Element 和 RenderObject 完成图形渲染工作。

  1. abstract class RenderObjectWidget extends Widget {
  2. @override
  3. RenderObjectElement createElement();
  4. @protected
  5. RenderObject createRenderObject(BuildContext context);
  6. @protected
  7. void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
  8. ...
  9. }
  • 实际上,RenderObjectWidget 本身并不负责这些对象的创建与更新 (Element、RenderObject)

对于 Element 的创建,Flutter 会在遍历 Widget 树时,调用 createElement 去同步 Widget 自身配置,从而生成对应节点的 Element 对象。而对于 RenderObject 的创建与更新,其实是在 RenderObjectElement 类中完成的。

  1. abstract class RenderObjectElement extends Element {
  2. RenderObject _renderObject;
  3. @override
  4. void mount(Element parent, dynamic newSlot) {
  5. super.mount(parent, newSlot);
  6. _renderObject = widget.createRenderObject(this);
  7. attachRenderObject(newSlot);
  8. _dirty = false;
  9. }
  10. @override
  11. void update(covariant RenderObjectWidget newWidget) {
  12. super.update(newWidget);
  13. widget.updateRenderObject(this, renderObject);
  14. _dirty = false;
  15. }
  16. ...
  17. }

真正的绘制和布局过程,则完全交由 RenderObject 完成:

  1. abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  2. ...
  3. void layout(Constraints constraints, { bool parentUsesSize = false }) {...}
  4. void paint(PaintingContext context, Offset offset) { }
  5. }

Widget、Element 与 RenderObject 在渲染过程中的关系:

image.png

图 3 界面示例

image.png

图 4 示例界面生成的“三棵树”