在探讨 Flutter 的 UI 系统之前,让我们先理解显示屏如何展示图形,这有助于我们理解 UI 系统的设计。显示屏中的每一个物理显示单位都是一个像素点,每一个像素点可以显示多种颜色,通过在不同的像素点上显示不同的颜色,最终构成完整的图像。像素是显示图像的基本单位。每一个像素点可以显示出1600万种颜色,由RGB三基色组成的屏幕,每个基本色(R、G、B)深度扩展至8 bit(位),即 2 的 24 次方为 1600 万色。像素点的大小取决于显示器的分辨率,相同面积的不同分辨率显示屏,其像素点大小各不相同。DPI(每英寸像素数)是通过将屏幕的横向或纵向像素数除以以英寸为单位的宽度或高度来计算的。更高的DPI意味着每个像素必须更小,以便能够被可用空间容纳,这意味着屏幕会更清晰,可以绘制更高细节级别的内容。为了更新显示画面,屏幕是以固定的频率刷新(从 GPU 取数据),例如一部手机屏幕的刷新频率是60Hz。在绘制完一帧图像后准备绘制下一帧时,显示器会发出一个垂直同步信号(如 VSync),60Hz的屏幕就会在一秒内发出60次这样的信号。这个信号主要是用于同步 CPU、GPU 和显示器的协作,通常,计算机系统中,CPU、GPU 和显示器通过特定的方式协作:CPU 将计算好的显示内容提交给 GPU,GPU 渲染后放入帧缓冲区,然后视频控制器按照同步信号从帧缓冲区取帧数据传递给显示器显示。由于最终的图形计算和绘制都是由相应的硬件来完成,操作系统通常屏蔽了直接操作硬件的指令。Flutter 通过提供一套 Dart API,然后在底层通过 skia 这种跨平台的绘制库实现了一套代码跨多端的解决方案。Flutter 的框架设计采用 Embedder(操作系统适配层)、Engine(渲染引擎及 Dart VM 层)和 Framework(UI SDK 层)整体三层的划分,每一层的组件定义都有着明确的边界,其向上提供的功能和向下依赖的能力也非常明确。Embedder 是操作系统适配层,实现了渲染 Surface 设置,线程设置,以及平台插件等平台相关特性的适配。Engine 层主要包含 Skia、Dart 和 Text,实现了 Flutter 的渲染引擎、文字排版、事件处理和 Dart 运行时等功能。Framework 层则是一个用 Dart 实现的 UI SDK,包含了动画、图形绘制和手势识别等功能。为了在绘制控件等固定样式的图形时提供更直观、更方便的接口,Flutter 还基于这些基础能力,根据 Material 和 Cupertino 两种视觉设计风格封装了一套 UI 组件库。通过 Flutter API,开发者可以直接使用这些组件库,实现多端高度一致的渲染体验且性能接近原生。在了解 Flutter UI系统和操作系统交互的原理之后,我们再来看看 Flutter 如何通过 Dart API 来操控画布。在 Flutter 开发中,页面中各界面元素以树的形式组织,即控件树。控件树中的每个控件创建不同类型的渲染对象,组成渲染对象树。但实际上 Flutter 中真正代表屏幕上显示元素的是 Element,Widget 只是描述 Element 的配置数据。UI树其实是由一个个独立的 Element 节点构成。组件最终的布局、渲染都是通过 RenderObject 来完成的,从创建到渲染的大体流程是:根据 Widget 生成 Element,然后创建相应的 RenderObject 并关联 Element.renderObject 属性上,最后再通过 RenderObject 来完成布局排列和绘制。Flutter 通过控件树中的每个控件创建不同类型的渲染对象,组成渲染对象树。然后 RenderObject 将所有的图层根据大小、层级、透明度等规则计算出最终的显示效果,将相同的图层归类合并,简化渲染树,提高渲染效率。合成完成后,Flutter 会将几何图层数据交由 Skia 引擎加工成二维图像数据,最终交由 GPU 进行渲染,完成界面的展示。