简单来讲,Flutter 从上到下可以分为三层:框架层、引擎层和嵌入层,下面我们分别介绍:
1. 框架层
Flutter Framework,即框架层。这是一个纯 Dart实现的 SDK,它实现了一套基础库,自底向上,我们来简单介绍一下:
底下两层(Foundation 和 Animation、Painting、Gestures)在 Google 的一些视频中被合并为一个dart UI层,对应的是Flutter中的dart:ui包,它是 Flutter Engine 暴露的底层UI库,提供动画、手势及绘制能力。
Rendering 层,即渲染层,这一层是一个抽象的布局层,它依赖于 Dart UI 层,渲染层会构建一棵由可渲染对象的组成的渲染树,当动态更新这些对象时,渲染树会找出变化的部分,然后更新渲染。渲染层可以说是Flutter 框架层中最核心的部分,它除了确定每个渲染对象的位置、大小之外还要进行坐标变换、绘制(调用底层 dart:ui )。
Widgets 层是 Flutter 提供的的一套基础组件库,在基础组件库之上,Flutter 还提供了 Material 和 Cupertino 两种视觉风格的组件库,它们分别实现了 Material 和 iOS 设计规范。
Flutter 框架相对较小,因为一些开发者可能会使用到的更高层级的功能已经被拆分到不同的软件包中,使用 Dart 和 Flutter 的核心库实现,其中包括平台插件,例如 camera 和 webview ,以及和平台无关的功能,例如 animations 。
2. 引擎层
Engine,即引擎层。毫无疑问是 Flutter 的核心, 该层主要是 C++ 实现,其中包括了 Skia 引擎、Dart 运行时(Dart runtime)、文字排版引擎等。在代码调用 dart:ui库时,调用最终会走到引擎层,然后实现真正的绘制和显示。
3. 嵌入层
Embedder,即嵌入层。Flutter 最终渲染、交互是要依赖其所在平台的操作系统 API,嵌入层主要是将 Flutter 引擎 ”安装“ 到特定平台上。嵌入层采用了当前平台的语言编写,例如 Android 使用的是 Java 和 C++, iOS 和 macOS 使用的是 Objective-C 和 Objective-C++,Windows 和 Linux 使用的是 C++。 Flutter 代码可以通过嵌入层,以模块方式集成到现有的应用中,也可以作为应用的主体。Flutter 本身包含了各个常见平台的嵌入层,假如以后 Flutter 要支持新的平台,则需要针对该新的平台编写一个嵌入层。
通常来说,开发者不需要感知到Engine和Embedder的存在(如果不需要调用平台的系统服务),Framework是开发者需要直接交互的,因而也在整个分层架构模型的最上层。
在 Flutter 中, widget 的功能是“描述一个UI元素的配置信息”,它就是说, Widget 其实并不是表示最终绘制在设备屏幕上的显示元素,所谓的配置信息就是 Widget 接收的参数,比如对于 Text 来讲,文本的内容、对齐方式、文本样式都是它的配置信息。下面我们先来看一下 Widget 类的声明:
@immutable // 不可变的 abstract class Widget extends DiagnosticableTree { const Widget({ this.key }); final Key? key; @protected @factory Element createElement(); @override String toStringShort() { final String type = objectRuntimeType(this, 'Widget'); return key == null ? type : '$type-$key'; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense; } @override @nonVirtual bool operator ==(Object other) => super == other; @override @nonVirtual int get hashCode => super.hashCode; static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } ... }
在Flutter中,可以说万事万物皆Widget。哪怕一个居中的能力,在传统的命令式UI开发中,通常会以一个属性的方式进行设置,而在Flutter中则抽象成一个名为Center的组件。此外,Flutter提倡激进式的组合开发,即尽可能通过一系列基础的Widget构造出你的目标Widget。
基于以上两个特点,Flutter的代码中将充斥着各种Widget,每一帧UI的更新都意味着部分Widget的重建。你可能会担心,这种设计是否过于臃肿和低效,其实恰恰相反,这正是Flutter能够进行高性能渲染的基石。Flutter之所以要如此设计也是基于以下两点事实有意而为之:
Widget Tree上的Widget节点越多,通过Diff算法得到的需要重建的部分就越精确、范围越小,而UI渲染的主要性能瓶颈就是Widget节点的重建。
Dart语言的对象模型 和 GC模型 对小对象的快读分配和回收做了优化,而Widget正是这种小对象。
既然 Widget 只是描述一个UI元素的配置信息,那么真正的布局、绘制是由谁来完成的呢?
Flutter 框架的的处理流程是这样的:
真正的布局和渲染逻辑在 Render 树中,Element 是 Widget 和 RenderObject 的粘合剂,可以理解为一个中间代理。
其中三棵树的各自的作用是:
这里需要注意:
StatelessWidget相对比较简单,它继承自widget类,重写了createElement()方法:
@override StatelessElement createElement() => StatelessElement(this);
StatelessElement 间接继承自Element类,与StatelessWidget相对应(作为其配置数据)。
StatelessWidget用于不需要维护状态的场景,它通常在build方法中通过嵌套其他 widget 来构建UI,在构建过程中会递归的构建其嵌套的 widget 。
下面是一个简单的例子:
class Echo extends StatelessWidget { const Echo({ Key? key, required this.text, this.backgroundColor = Colors.grey, //默认为灰色 }):super(key:key); final String text; final Color backgroundColor; @override Widget build(BuildContext context) { return Center( child: Container( color: backgroundColor, child: Text(text), ), ); } }
然后我们可以通过如下方式使用它:
Widget build(BuildContext context) { return Echo(text: "hello world"); }
按照惯例,widget 的构造函数参数应使用命名参数,命名参数中的必需要传的参数要添加required关键字,这样有利于静态代码分析器进行检查;在继承 widget 时,第一个参数通常应该是Key。另外,如果 widget 需要接收子 widget ,那么child或children参数通常应被放在参数列表的最后。同样是按照惯例, widget 的属性应尽可能的被声明为final,防止被意外改变。
build方法有一个context参数,它是BuildContext类的一个实例,表示当前 widget 在 widget 树中的上下文,每一个 widget 都会对应一个 context 对象。
实际上,context是当前 widget 在 widget 树中位置中执行”相关操作“的一个句柄(handle), 比如它提供了从当前 widget 开始向上遍历 widget 树以及按照 widget 类型查找父级 widget 的方法。
下面是在子树中获取父级 widget 的一个示例:
class ContextRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Context测试"), ), body: Container( child: Builder(builder: (context) { // 在 widget 树中向上查找最近的父级`Scaffold` widget Scaffold scaffold = context.findAncestorWidgetOfExactType(); // 直接返回 AppBar的title, 此处实际上是Text("Context测试") return