前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Widget,构建Flutter界面的基石

Widget,构建Flutter界面的基石

作者头像
拉维
发布2019-08-12 15:59:59
1.3K0
发布2019-08-12 15:59:59
举报
文章被收录于专栏:iOS小生活

首先我来分享一张来自Flutter官方的架构图:

从该架构图中可以看出,Widget是整个视图描述的基础。

那么,Widget到底是什么呢?

Widget是Flutter开发框架中最基本的概念。前端框架中常见的名词,比如视图(View)、视图控制器(View Controller)、应用(Application)、布局(Layout)等,在Flutter中都是Widget。

事实上,Flutter的核心设计思想便是“一切皆Widget”

Widget渲染过程

在进行APP开发时,我们往往会关注的一个问题是:如何结构化地组织视图数据,提供给渲染引擎,最终完成界面显示。

通常情况下,不同的UI框架中会以不同的方式处理这一问题,但无一例外地都会用到视图树(View Tree)的概念。而Flutter将视图树的概念进行了扩展,把视图数据的组织和渲染抽象为三部分,即Widget、Element和RenderObject

这三部分之间的关系,如下所示:

Widget

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

在页面渲染上,Flutter将“Simple is best”这一理念做到了极致。为什么这么说呢?Flutter将Widget设计成不可变的,所以当视图渲染的配置信息发生变化时,Flutter会以重新创建Widget树的方式进行数据更新,以数据来驱动UI构建的方式简单高效

但是这样做的缺点是,因为涉及到大量对象的销毁和重建,所以会对垃圾回收造成压力。不过,Widget本身并不涉及实际渲染位图,所以它只是一份轻量级的数据结构,重建的成本很低

另外,由于Widget的不可变性,可以以较低成本进行渲染节点复用,因此在一个真实的渲染树中可能存在不同的Widget对应同一个渲染节点的情况,这无疑又降低了重建UI的成本。

Element

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

Flutter的渲染过程,可以分为以下三步:

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

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

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

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

Element是可复用的,只要Widget前后类型一样。比如Widget是蓝色的,重建后变成红色了,那Element就会复用。所以是多个Widget(销毁前后)会对应一个Element

这,就是Element树存在的意义。

RenderObject

从其名字,我们就可以很直观地知道,RenderObject是主要负责实现视图渲染的对象。

通过前文我们知道,Flutter通过控件树(Widget Tree)中的每个控件(Widget)创建不同类型的渲染对象,组成渲染对象树。

渲染对象树在Flutter中的展示过程分为四个阶段:布局、绘制、合成和渲染。其中,布局和绘制在RenderObject中完成,Flutter采用深度优先机制遍历渲染对象树,确定树中各对象的位置和尺寸,并把他们绘制到不同的图层上。绘制完毕后,合成和渲染工作则交给Skia搞定。关于布局、绘制、合成和渲染的详细介绍,可以参考Flutter区别于其他技术的关键是什么?

Flutter 通过引入Widget、Element和RenderObject这三个概念,把原本从视图数据到视图渲染的复杂构建过程拆分得更简单直接,在易于集中治理的同时,保证了较高的渲染效率。

RenderObjectWidget介绍

到目前为止,我们都应该已经接触和使用过StatelessWidget和StatefulWidget了。不过,StatelessWidget和StatefulWidget只不过是用来组装控件的容器,并不负责组件最后的布局和绘制。在Flutter中,布局和绘制工作实际上是在Widget的另一个子类RenderObjectWidget内完成的

首先我们来看一下RenderObjectWidget类的定义:

代码语言:javascript
复制
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 类中完成的。

代码语言:javascript
复制
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完成

代码语言:javascript
复制
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。可以在Flutter区别于其他技术的关键是什么?这篇文章中查看这部分内容的详细介绍,这里就不赘述了。

接下来,我以下面的界面示例为例,与你说明Widget、Element与RenderObject在渲染过程中的关系。在下面的例子中,一个Row容器放置了4个子Widget,左边是Image,而右边是一个Column容器下排布的两个Text。

那么,在Flutter遍历完Widget树,创立了各个子Widget对应的Element的同时,也创建了与之关联的、负责实际布局与绘制的RenderObject。

总结

1,Widget是Flutter世界里对视图的一种结构化描述,里面存储的是有关视图渲染的配置信息;Element则是Widget的一个实例化对象,将Widget树的变化做了抽象,能够做到只将真正需要修改的部分同步到真实的RenderObject树中,最大程度地优化了从结构化的配置信息到完成最终渲染的过程;而RenderObject,则负责实现视图的最终呈现,通过布局、绘制完成界面的展示

2,在日常开发中,绝大多数情况下,我们只需要了解各种Widget特性及使用方法,而无需关心Element及RenderObject。因为Flutter已经帮我们做了大量的优化工作,因此我们只需要在上层代码完成各类Widget的组装配置,其他的事情完全交给Flutter就可以了。

3,Widget是不可变的,因此只要有一丁点儿的改变,Widget都要销毁重新创建。而只要Widget的前后类型一样,那么其对应的Element就是可以复用的,比如Widget原本是蓝色,重建后变成红色了,那么其对应的Element就会复用。所以说,多个Widget(销毁前后)会对应一个Element

4,渲染对象树(RenderObject Tree)在Flutter中的展示过程分为四步:布局、绘制、合成、渲染。布局和绘制在RenderObject内部完成,合成和渲染则交由Skia完成。绘制强调绘图命令,调用GPU之前执行;渲染强调最终呈现,需要调用GPU

以上。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
GPU 云服务器
GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于生成式AI,自动驾驶,深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档