1.Widget概念
- 字面意思就是 装饰物/小部件,在Flutter中几乎所有的对象都是一个 widget。
- Widget 的功能是“描述一个UI元素的配置信息”(所谓的配置信息就是 Widget 接收的参数,比如对于 Text 来讲,文本的内容、对齐方式、文本样式都是它的配置信息)。
- 与原生相比,原生开发中的“控件”通常只是指UI元素,Flutter 中的 widget 的概念更广泛,它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的
GestureDetector
、用于APP主题数据传递的Theme
等等。
2.Widget中的接口
Widget
类本身是一个抽象类。其中最核心的就是定义了createElement()
接口,在 Flutter 开发中,我们一般都不用直接继承Widget
类来实现一个新组件,相反,我们通常会通过继承StatelessWidget
或StatefulWidget
来间接继承widget
类来实现。
Widget源码如下:
// 不可变的
abstract class Widget extends DiagnosticableTree {const Widget({ this.key });final Key? key;Element createElement();String toStringShort() {final String type = objectRuntimeType(this, 'Widget');return key == null ? type : '$type-$key';}void debugFillProperties(DiagnosticPropertiesBuilder properties) {super.debugFillProperties(properties);properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;} bool operator ==(Object other) => super == other; int get hashCode => super.hashCode;static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType&& oldWidget.key == newWidget.key;}...
}
@immutable
代表 Widget 是不可变的,这会限制 Widget 中定义的属性(即配置信息)必须是不可变的(final)。widget
类继承自DiagnosticableTree
,DiagnosticableTree
即“诊断树”,主要作用是提供调试信息。Key
: 这个key
属性类似于 React/Vue 中的key
,主要的作用是决定是否在下一次build
时复用旧的 widget ,决定的条件在canUpdate()
方法中。createElement()
:“一个 widget 可以对应多个Element
”;Flutter 框架在构建UI树时,会先调用此方法生成对应节点的Element
对象。此方法是 Flutter 框架隐式调用的,在我们开发过程中基本不会调用到。debugFillProperties(...)
复写父类的方法,主要是设置诊断树的一些特性。canUpdate(...)
是一个静态方法,它主要用于在 widget 树重新build
时复用旧的 widget 。通过源码可知:只要newWidget
与oldWidget
的runtimeType
和key
同时相等时就会用new widget
去更新Element
对象的配置,否则就会创建新的Element
。
3.Flutter中的四棵树
- Widget 树
- Element 树
- Render 树(渲染树)
- Layer 树
既然 Widget 只是描述一个UI元素的配置信息,那么真正的布局、绘制是由谁来完成的呢?Flutter 框架的处理流程是这样的:
- 根据 Widget 树生成一个 Element 树。(Element 树中的节点都继承自
Element
类) - 根据 Element 树生成 Render 树。(渲染树中的节点都继承自
RenderObject
类) - 根据渲染树生成 Layer 树,然后上屏显示。(Layer 树中的节点都继承自
Layer
类)
真正的布局和渲染逻辑在 Render 树中,Element 是 Widget 和 RenderObject 的粘合剂,可以理解为一个中间代理。
我们通过一个例子来说明,假设有如下 Widget 树:
Container( // 一个容器 widgetcolor: Colors.blue, // 设置容器背景色child: Row( // 可以将子widget沿水平方向排列children: [Image.network('https://www.example.com/1.png'), // 显示图片的 widgetconst Text('A'),],),
);
注意,如果 Container 设置了背景色,Container 内部会创建一个新的 ColoredBox 来填充背景,相关逻辑如下:
if (color != null)current = ColoredBox(color: color!, child: current);
而 Image 内部会通过 RawImage 来渲染图片、Text 内部会通过 RichText 来渲染文本,所以最终的 Widget树、Element 树、渲染树结构如下图所示:
这里需要注意:
- 三棵树中,Widget 和 Element 是一一对应的,但并不和 RenderObject 一一对应。比如
StatelessWidget
和StatefulWidget
都没有对应的 RenderObject。 - 渲染树在上屏前会生成一棵 Layer 树,这个会在后面原理篇再介绍,目前只需要记住以上三棵树就行。
4.StatelessWidget
4.1 源码
abstract class StatelessWidget extends Widget {/// Initializes [key] for subclasses.const StatelessWidget({ Key? key }) : super(key: key);/// 重写了 widget 类的 createElement()方法,返回了StatelessElementStatelessElement createElement() => StatelessElement(this);Widget build(BuildContext context);
}
- 无状态的Widget,用于不需要维护状态的场景
- 继承自
widget
类,重写了createElement()
方法- 通常在
build
方法中通过嵌套其他 widget 来构建UI,在构建过程中会递归的构建其嵌套的 widget
StatelessWidget
相对比较简单,它继承自widget
类,重写了createElement()
方法:
StatelessElement createElement() => StatelessElement(this);
StatelessElement
间接继承自Element
类,与StatelessWidget
相对应(作为其配置数据)。
StatelessWidget
用于不需要维护状态的场景,它通常在build
方法中通过嵌套其他 widget 来构建UI,在构建过程中会递归的构建其嵌套的 widget 。
我们看一个简单的例子:
class Echo exte