Flutter核心技术与实战 09 | Widget,构建Flutter界面的基石

Flutter 的核心设计思想即是“一切皆 Widget”前端

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

Widget 渲染过程

一般状况下,都会用到视图树(View Tree)的概念。而 Flutter 将视图树的概念进行了扩展,把视图数据的组织和渲染抽象为三部分,即 Widget,Element 和 RenderObject。前端框架

Widget

Widget 是 Flutter 世界里对视图的一种结构化描述,你能够把它看做是前端中的“控件”或“组件”。Widget 是控件实现的基本逻辑单位,里面存储的是有关视图渲染的配置信息,包括布局、渲染属性、事件响应信息等。数据结构

Flutter 将 Widget 设计成不可变的,因此当视图渲染的配置信息发生变化时,Flutter 会选择重建 Widget 树的方式进行数据更新,以数据驱动 UI 构建的方式简单高效。但,这样作的缺点是,由于涉及到大量对象的销毁和重建,因此会对垃圾回收形成压力。框架

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

Element

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

Flutter 渲染过程,能够分为这么三步:布局

  • 首先,经过 Widget 树生成对应的 Element 树;
  • 而后,建立相应的 RenderObject 并关联到 Element.renderObject 属性上;
  • 最后,构建成 RenderObject 树,以完成最终的渲染。

那为何须要增长中间的这层 Element 树呢?直接由 Widget 命令 RenderObject 去干活儿很差吗?性能

答案是,能够,但这样作会极大地增长渲染带来的性能损耗。ui

由于 Widget 具备不可变性,但 Element 倒是可变的。实际上,Element 树这一层将 Widget 树的变化(相似 React 虚拟 DOM diff)作了抽象,能够只将真正须要修改的部分同步到真实的 RenderObject 树中,最大程度下降对真实渲染视图的修改,提升渲染效率,而不是销毁整个渲染视图树重建。

RenderObject

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

Flutter 经过控件树(Widget 树)中的每一个控件(Widget)建立不一样类型的渲染对象,组成渲染对象树。

渲染对象树在 Flutter 的展现过程分为四个阶段,即布局、绘制、合成和渲染。 其中,布局和绘制在 RenderObject 中完成,Flutter 采用深度优先机制遍历渲染对象树,肯定树中各个对象的位置和尺寸,并把它们绘制到不一样的图层上。绘制完毕后,合成和渲染的工做则交给 Skia 搞定。

部分源码分析

abstract class RenderObjectWidget extends Widget {
  @override
  RenderObjectElement createElement();
  @protected
  RenderObject createRenderObject(BuildContext context);
  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
  ...
}
复制代码

RenderObjectWidget 是一个抽象类。这个类同时拥有建立 Element、RenderObject,以及更新 RenderObject 的方法。

但实际上,RenderObjectWidget 自己并不负责这些对象的建立与更新。

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

abstract class RenderObjectElement extends Element {
  RenderObject _renderObject;

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);
    attachRenderObject(newSlot);
    _dirty = false;
  }
   
  @override
  void update(covariant RenderObjectWidget newWidget) {
    super.update(newWidget);
    widget.updateRenderObject(this, renderObject);
    _dirty = false;
  }
  ...
}
复制代码

在 Element 建立完毕后,Flutter 会调用 Element 的 mount 方法。在这个方法里,会完成与之关联的 RenderObject 对象的建立,以及与渲染树的插入工做,插入到渲染树后的 Element 就能够显示到屏幕中了。

若是 Widget 的配置数据发生了改变,那么持有该 Widget 的 Element 节点也会被标记为 dirty。在下一个周期的绘制时,Flutter 就会触发 Element 树的更新,并使用最新的 Widget 数据更新自身以及关联的 RenderObject 对象,接下来便会进入 Layout 和 Paint 的流程。而真正的绘制和布局过程,则彻底交由 RenderObject 完成。

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  ...
  void layout(Constraints constraints, { bool parentUsesSize = false }) {...}
  
  void paint(PaintingContext context, Offset offset) { }
}
复制代码

布局和绘制完成后,接下来的事情就交给 Skia 了。在 VSync 信号同步时直接从渲染树合成 Bitmap,而后提交给 GPU。

扩展知识

React:JSX->虚拟DOM->浏览器DOM

React Native:JSX->虚拟DOM->Android/iOS原生控件

flutter:Widget->Element(相似虚拟DOM,只是一种数据结构)-> RenderObject 交给底层渲染

Element是可复用的,只要Widget先后类型同样。好比Widget是蓝色的,重建后变红色了,Element是会复用的。因此是多个Widget(销毁先后)会对应一个Element。而Widget是不可变的,一旦改变就会销毁重建。

相关文章
相关标签/搜索