likes
comments
collection
share

Flutter UI系统

作者站长头像
站长
· 阅读数 0

一、屏幕和图像

在介绍 Flutter 的 UI 系统之前,我们先来了解下显示屏是如何显示图形的,这有助于理解 UI 系统的设计。

1.1 基本单位 - 像素

可视化的所有的一切在本质上都是依靠绘制的,而绘制的基本单位则由显示屏中一个个物理显示单位组成,每一个单位我们可以称之为一个物理像素点,而每一个像素点可以发出多种颜色,显示器成像的原理就是在不同的物理像素点上显示不同的颜色,最终构成完整的图像。

Flutter UI系统

(图来源于www.bilibili.com/video/BV1dA…视频中22s截图)

  • RGB 排列:每个像素都是由3个紧密相邻的RGB(红绿蓝的英文单词首字母)子像素整齐排列组成,这种排列方式常见于 LCD 屏幕。
  • Delta 排列:相比标准 RGB 排列,三种颜色的子像素数量各减少三分之一,每个像素呈 R-G、G-B 或 B-R 排列,六个像素共用周围一个子像素,实际像素密度大约只有标准 RGB 排列的 70%。
  • 钻石 Pentile 排列:Pentile 排列每一行的子像素数量都少了三个,最终总的子像素数量则减少1/3。
  • 京东方排列:属于 Delta 的一个变种,设计方案出自京东方。把一个绿色像素拆成两个子像素。其像素点排列方式看起来就像红绿蓝小人,小眼睛厚嘴唇,就像周冬雨的表情,又称为周冬雨排列。

Flutter UI系统

(图来源于网络:wantubizhi.com/image.aspx?…)

上面介绍的布局方案 RGB 排列常见于 LCD 屏幕,其他都是为了 OLED 屏幕而诞生的,因为 OLED 是自发光,属于有机电致发光,而有机物容易失效且会自分解,失效的有机物便不能再发光。所以为了延长有机物的寿命诞生了各种排列布局方案增长 OLED 屏幕的寿命。

1.2 屏幕分辨率

一个像素点可以显示出 1600 万种颜色,由RGB三基色组成的屏幕,每个基本色(R、G、B)深度扩展至8 bit(位),即 2 的 24 次方为 1600 万色,颜色深度越深,所能显示的色彩更加丰富靓丽。那么像素点有多大呢?主要取决于显示器的分辨率,相同面积不同分辨率的显示屏,其像素点大小就不相同。

Flutter UI系统

(图来源于www.dazhuanlan.com/2019/10/12/…)

1.3 DPI

DPI 也常被称为 PPI (每英寸像素数),它是通过将屏幕的横向或纵向像素数除以以英寸为单位的宽度或高度来计算的。更高的 DPI 意味着每个像素必须更小,以便能够被可用空间容纳,这意味着屏幕会更清晰,屏幕可以绘制的细节级别更高。

Flutter UI系统

(图来源于www.dazhuanlan.com/2019/10/12/…)

Flutter UI系统

(图来源于www.dazhuanlan.com/2019/10/12/…)

1.4 显示原理

为了更新显示画面,屏幕是以固定的频率刷新(从 GPU 取数据),比如一部手机屏幕的刷新频率是 60Hz。当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(如 VSync), 60Hz的屏幕就会一秒内发出 60 次这样的信号。而这个信号主要是用于同步 CPU、GPU 和显示器的。一般地来说,计算机系统中,CPU、GPU 和显示器以一种特定的方式协作:CPU 将计算好的显示内容提交给 GPU,GPU 渲染后放入帧缓冲区,然后视频控制器按照同步信号从帧缓冲区取帧数据传递给显示器显示。

Flutter UI系统

(图来源于www.jianshu.com/p/7b7975158…)

由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。

CPU 和 GPU 的任务是各有偏重的,CPU 主要用于基本数学和逻辑计算,而 GPU 主要执行和图形处理相关的复杂的数学,如矩阵变化和几何计算,GPU 的主要作用就是确定最终输送给显示器的各个像素点的色值。

1.5 UI系统

由于最终的图形计算和绘制都是由相应的硬件来完成,而直接操作硬件的指令通常都会有操作系统屏蔽,开发者通常不会直接面对硬件,通常硬件编码开发效率低,操作系统屏蔽了这些底层硬件操作后会提供一些封装后的API供操作系统之上的应用调用。

Flutter UI系统

对于应用开发者来说,直接调用操作系统提供的API是复杂低效的,因为操作系统提供的API往往比较基础,直接调用需要了解API的很多细节。所以开发GUI程序的编程语言都会在操作系统之上再封装一层,将操作系统原生API封装在一个编程框架和模型中,然后定义一种简单的开发规则来开发GUI应用程序,而这一层抽象,正是“UI”系统,如Android SDK正是封装了Android操作系统API,提供了一个“UI描述文件XML+Java操作DOM”的UI系统,而iOS的UIKit 对View的抽象也是一样的,他们都将操作系统API抽象成一个基础对象(如用于2D图形绘制的Canvas),然后再定义一套规则来描述UI,如UI树结构,UI操作的单线程原则等。

Flutter UI系统

而Flutter的原理正是如此,它提供了一套Dart API,然后在底层通过 skia 这种跨平台的绘制库(内部会调用操作系统API)实现了一套代码跨多端。

二、Flutter UI系统

Flutter 自带渲染引擎,提供了一整套从底层渲染逻辑到上层开发语言的完整解决方案:视图渲染完全闭环在其框架内部,不依赖于底层操作系统提供的任何组件,从根本上保证了视图渲染在 Android 和 iOS 上的高度一致性。

Flutter UI系统

那么 Flutter 是怎么实现这套解决方案的呢?我们先来看看 Flutter 的框架设计。

2.1 Flutter框架设计

Flutter UI系统

(图来源于flutter.cn/docs/resour…)

Flutter 采用 Embedder(操作系统适配层)、Engine(渲染引擎及 Dart VM 层)和 Framework(UI SDK 层)整体三层的划分。上图可以看出,Flutter 框架每一层的组件定义都有着明确的边界,其向上提供的功能和向下依赖的能力也非常明确。

2.2 Embedder - 操作系统适配层

Embedder 是操作系统适配层,实现了渲染 Surface 设置,线程设置,以及平台插件等平台相关特性的适配。从这里我们可以看到,Flutter 平台相关特性并不多,这就使得从框架层面保持跨端一致性的成本相对较低。

Flutter UI系统

(图来源于flutter.cn/docs/resour…)

2.3 Engine - 渲染引擎及 Dart VM 层

Engine 层主要包含 Skia、Dart 和 Text,实现了 Flutter 的渲染引擎、文字排版、事件处理和 Dart 运行时等功能。Skia 和 Text 为上层接口提供了调用底层渲染和排版的能力,Dart 则为 Flutter 提供了运行时调用 Dart 和渲染引擎的能力。而 Engine 层的作用,则是将它们组合起来,从它们生成的数据中实现视图渲染。

Flutter UI系统

(图来源于flutter.cn/docs/resour…)

2.4 Framework - UI SDK 层

Framework 层则是一个用 Dart 实现的 UI SDK,包含了动画、图形绘制和手势识别等功能。为了在绘制控件等固定样式的图形时提供更直观、更方便的接口,Flutter 还基于这些基础能力,根据 Material 和 Cupertino 两种视觉设计风格封装了一套 UI 组件库。我们在开发 Flutter 的时候,可以直接使用这些组件库。

Flutter UI系统

(图来源于flutter.cn/docs/resour…)

2.5 绘制流程

在了解 Flutter 框架设计之后,我们再来看看其实现原理,从 Flutter 官网上可以发现有原理介绍图。

Flutter UI系统

(图来源于flutter.cn/docs/resour…)

通过原理图拆解我们可以了解到,GPU 通过 VSync 信号同步 UI线程,UI线程使用 Dart 来构建抽象的视图结构,然后提交给 Skia 引擎渲染为 GPU 所需要的数据,通过 OpenGL 或者 Vulkan 提供给 GPU,在 GPU 线程进行图层合成。

我们还可以根据 VSync 信号来整理一下流程。

Flutter UI系统

(图来源于www.debugger.wiki/article/htm…)

Flutter 通过在两个硬件时钟的 VSync 信号之间计算并合成视图数据,Skia 交给 GPU 渲染:UI 线程使用 Dart 来构建视图结构数据,在 GPU 线程进行图层合成,随后交给 Skia 引擎加工成 GPU 数据,通过 OpenGL 最终提供给 GPU 渲染。

Flutter 的整个绘制流程是基于 Dart API 也是调用操作系统API,所以客户端仅提供一块画布即可获得从业务逻辑到功能呈现的多端高度一致的渲染体验且性能接近原生。

三、Flutter UI 系统的基石

在了解 Flutter UI系统和操作系统交互的这一部分原理之后,再来看看 Flutter 是如何通过 Dart API 来操控画布的。

3.1 页面结构

通常我们在一个画布上画画时,都需要对画作有一个大致的轮廓的设计即线稿,将画布分为一块块区域,而Flutter 开发时也是一样。

Flutter UI系统

通过上图 Flutter 示例页面的结构中可以看到页面树结构是由一个个控件所形成的。然而在 Flutter 体系结构中,真正做组件渲染在屏幕上这个任务的并非在 控件层(Widget)层,而是在渲染(Rendering)层。渲染层有着极为重要的Element 树和 RenderingObject 树两棵树。

3.2 三棵树

Flutter UI系统

(图来源于book.flutterchina.club/chapter14/e…)

Flutter 页面中各界面元素(Widget)以树的形式组织,即控件树。Flutter 通过控件树中的每个控件创建不同类型的渲染对象,组成渲染对象树。但实际上Flutter中真正代表屏幕上显示元素的是 Element ,Widget 只是描述 Element 的配置数据。所以UI树其实是由一个个独立的Element节点构成。

组件最终的 Layout、渲染都是通过 RenderObject 来完成的,从创建到渲染的大体流程是:根据Widget 生成 Element,然后创建相应的 RenderObject 并关联到 Element.renderObject 属性上,最后再通过 RenderObject 来完成布局排列和绘制。

3.3 总结

Flutter 通过控件树中的每个控件创建不同类型的渲染对象,组成渲染对象树。然后 RenderObject 将所有的图层根据大小、层级、透明度等规则计算出最终的显示效果,将相同的图层归类合并,简化渲染树,提高渲染效率。合成完成后,Flutter 会将几何图层数据交由 Skia 引擎加工成二维图像数据,最终交由 GPU 进行渲染,完成界面的展示。

参考资料:

Flutter官网

Flutter实战

屏幕排列讲解

像素,点和分辨率

屏幕显示图像原理

作者:林凯锋