骐骥一跃,不能十步;驽马十驾,功在不舍。
Flutter学习之二 第一个App
前言
不管有没有的写过或者学习过flutter的coder,多少都听过flutter的一切皆组件。是的,没错一切皆组件,flutter中几乎所有的对象都是一个Widget。记住!Flutter 中万物皆为Widget!
正文
一、概念
widget它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetector 、用于APP主题数据传递的 Theme 等等,而原生开发中的控件通常只是指UI元素。在后面的内容中,我们在描述UI元素时可能会用到“控件”、“组件”这样的概念,读者心里需要知道他们就是 widget ,只是在不同场景的不同表述而已。由于 Flutter 主要就是用于构建用户界面的,所以,在大多数时候,读者可以认为 widget 就是一个控件,不必纠结于概念。
二、Widget API简介
在 Flutter 中, widget 的功能是“描述一个UI元素的配置信息”,它就是说, Widget 其实并不是表示最终绘制在设备屏幕上的显示元素,所谓的配置信息就是 Widget 接收的参数,下面是 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;
}
static int _debugConcreteSubtype(Widget widget) {
return widget is StatefulWidget ? 1 :
widget is StatelessWidget ? 2 :
0;
}
}
-
@immutable
代表 Widget 是不可变的,这会限制 Widget 中定义的属性(即配置信息)必须是不可变的(final)
,为什么不允许 Widget 中定义的属性变化呢?这是因为,Flutter 中如果属性发生则会重新构建Widget树
,即重新创建新的 Widget 实例来替换旧的 Widget 实例,所以允许 Widget 的属性变化是没有意义的,因为一旦 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
,其实具体来说,应该是:是否用新的 widget 对象去更新旧UI树上所对应的Element对象的配置;通过其源码我们可以看到,只要newWidget
与oldWidget
的runtimeType
和key
同时相等时就会用new widget
去更新Element
对象的配置,否则就会创建新的Element
。
三、Flutter中的四颗树
既然 Widget 只是描述一个UI元素的配置信息,那么真正的布局、绘制是由谁来完成的呢?Flutter 框架的的处理流程是这样的:
1、根据 Widget 树生成一个 Element 树,Element 树中的节点都继承自
Element
类。
2、根据 Element 树生成 Render 树(渲染树),渲染树中的节点都继承自RenderObject
类。
3、根据渲染树生成 Layer 树,然后上屏显示,Layer 树中的节点都继承自Layer
类。
真正的布局和渲染逻辑在 Render
树中,Element
是 Widget
和 RenderObject
的粘合剂,可以理解为一个中间代理。我们通过一个例子来说明,假设有如下 Widget 树:
Container( // 一个容器 widget
color: Colors.blue, // 设置容器背景色
child: Row( // 可以将子widget沿水平方向排列
children: [
Image.network('https://www.example.com/1.png'), // 显示图片的 widget
const Text('A'),
],
),
);
注意,如果 Container
设置了背景色,Container
内部会创建一个新的 ColoredBox
来填充背景,相关逻辑如下:
if (color != null)
current = ColoredBox(color: color!, child: current);
而 Image 内部会通过 RawImage 来渲染图片、Text 内部会通过 RichText 来渲染文本,所以最终的 Widget树、Element 树、渲染树结构如图1所示:
这里需要注意:
- 三棵树中,Widget 和 Element 是一一对应的,但并不和 RenderObject 一一对应。比如
StatelessWidget
和StatefulWidget
都没有对应的 RenderObject。 - 渲染树在上屏前会生成一棵 Layer 树,这个比较难理解,可以先记着有这么个东西,我学会了再写QAQ。
Flutter学习之四 StatelessWidget 和 StatefulWidget