一、Widget
1.1 canUpdate方法
canUpdate(...)
是一个静态方法,它主要用于在 widget 树重新build
时复用旧的 widget。
- 根本作用:是否用新的 widget 对象去更新旧UI树上所对应的
Element
对象的配置。 - 判断规则:只要
newWidget
与oldWidget
的runtimeType
和key
同时相等时就会用new widget
去更新Element
对象的配置,否则就会创建新的Element
。
1.2 Flutter中的四棵树
1.2.1 基本联系与职责
- 根据 Widget 树生成一个 Element 树,Element 树中的节点都继承自
Element
类。 - 根据 Element 树生成 Render 树(渲染树),渲染树中的节点都继承自
RenderObject
类。 - 根据渲染树生成 Layer 树,然后上屏显示,Layer 树中的节点都继承自
Layer
类。
- 布局和渲染逻辑在
Render
树中。 Element
是 Widget 和 RenderObject 的粘合剂。
1.2.2 例子
1
2
3
4
5
6
7
8
9
Container( // 一个容器 widget
color: Colors.blue, // 设置容器背景色
child: Row( // 可以将子widget沿水平方向排列
children: [
Image.network('https://www.example.com/1.png'), // 显示图片的 widget
const Text('A'),
],
),
);
注意,如果 Container 设置了背景色,Container 内部会创建一个新的 ColoredBox 来填充背景,相关逻辑如下:
1
2
if (color != null)
current = ColoredBox(color: color!, child: current);
- Image 内部会通过 RawImage 来渲染图片
- Text 内部会通过 RichText 来渲染文本
1.2.3 四棵树的关系
- Widget 和 Element 是一一对应的。
- Widget 并不和 RenderObject 一一对应。比如
StatelessWidget
和StatefulWidget
都没有对应的 RenderObject。 - 渲染树在上屏前会生成一棵 Layer 树。
1.3 Widget构造函数
按照惯例,widget 的构造函数参数应使用命名参数,命名参数中的必需要传的参数要添加required
关键字,这样有利于静态代码分析器进行检查;在继承 widget 时,第一个参数通常应该是Key
。另外,如果 widget 需要接收子 widget ,那么child
或children
参数通常应被放在参数列表的最后。同样是按照惯例, widget 的属性应尽可能的被声明为final
,防止被意外改变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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),
),
);
}
}
二、StatelessWidget
2.1 基本概念
StatelessWidget
继承自widget
类,重写了createElement()
方法:
1
2
@override
StatelessElement createElement() => StatelessElement(this);
StatelessElement
间接继承自Element
类。
- 作用:
StatelessWidget
用于不需要维护状态的场景,它通常在build
方法中通过嵌套其他 widget 来构建UI,在构建过程中会递归的构建其嵌套的 widget 。
2.2 Context
build
方法有一个context
参数,它是BuildContext
类的一个实例,表示当前 widget 在 widget 树中的上下文,每一个 widget 都会对应一个 context 对象(因为每一个 widget 都是 widget 树上的一个节点)。
三、StatefulWidget
3.1 基本概念
和StatelessWidget
一样,StatefulWidget
也是继承自widget
类,并重写了createElement()
方法,不同的是返回的Element
对象并不相同;另外StatefulWidget
类中添加了一个新的接口createState()
。
1
2
3
4
5
6
7
8
9
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);
@protected
State createState();
}
StatefulElement
中可能会多次调用createState()
来创建状态(State)对象。 多次调用时机:
- 当一个 StatefulWidget 同时插入到 widget 树的多个位置时,Flutter 框架就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。
3.2 三者关系
- 当一个 StatefulWidget 同时插入到 widget 树的多个位置时,一个位置生成一个独立的State实例。
- State 对象和
StatefulElement
具有一一对应的关系。 - State 对象状态发生改变时,可能会重新构建新的 widget 实例。
3.3 State
State 中的保存的状态信息:
- 在 widget 构建时可以被同步读取。
- 在 widget 生命周期中可以被改变,当State被改变时,可以手动调用其setState()方法通知Flutter 框架状态发生改变,Flutter 框架在收到消息后,会重新调用其build方法重新构建 widget 树,从而达到更新UI的目的。
3.3.1 两个属性
widget
,它表示与该 State 实例关联的 widget 实例,由Flutter 框架动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI树上的某一个节点的 widget 实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果 widget 被修改了,Flutter 框架会动态设置State. widget 为新的 widget 实例。context
。StatefulWidget对应的 BuildContext,作用同StatelessWidget 的BuildContext。
3.3.2 生命周期
创建时:
1
2
3
I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build
热重载时:
1
2
3
I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget
I/flutter ( 5436): build
在 widget 树中移除,然后热重载:
1
2
3
4
5
6
Widget build(BuildContext context) {
//移除计数器
//return CounterWidget ();
//随便返回一个Text()
return Text("xxx");
}
1
2
3
I/flutter ( 5436): reassemble
I/flutter ( 5436): deactive
I/flutter ( 5436): dispose
3.3.3 initState
调用时机:当 widget 第一次插入到 widget 树时会被调用,对于每一个State对象,Flutter 框架只会调用一次该回调。 作用:通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。
3.3.4 didChangeDependencies
调用时机:
- 当State对象的依赖发生变化时会被调用。
- 组件第一次被创建后挂载的时候(包括重创建)对应的didChangeDependencies也会被调用。
作用:当系统语言 Locale 或应用主题改变时,Flutter 框架会通知 widget 调用此回调。
3.3.5 build
调用时机:
- 在调用initState()之后。
- 在调用didUpdateWidget()之后。
- 在调用setState()之后。
- 在调用didChangeDependencies()之后。
- 在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其他位置之后。
作用:主要是用于构建 widget 子树。
3.3.6 didUpdateWidget
调用时机:在 widget 重新构建时,Flutter 框架会调用widget.canUpdate
来检测 widget 树中同一位置的新旧节点,然后决定是否需要更新,如果widget.canUpdate
返回true
则会调用此回调。 作用:节省性能开销。
3.3.7 deactivate
调用时机:当 State 对象从树中被移除时。
3.3.8 dispose
调用时机:当 State 对象从树中被永久移除时;如果deactivate被调用后,移除的子树没有重新插入到树中则紧接着会调用dispose()方法。 作用:通常在此回调中释放资源。
3.4 StatefulWidget 生命周期图示
3.5 @mustCallSuper
在继承StatefulWidget
重写其方法时,对于包含@mustCallSuper
标注的父类方法,都要在子类方法中调用父类方法。
四、在 widget 树中获取State对象
4.1 第一种办法:通过Context获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class GetStateObjectRoute extends StatefulWidget {
const GetStateObjectRoute({Key? key}) : super(key: key);
@override
State<GetStateObjectRoute> createState() => _GetStateObjectRouteState();
}
class _GetStateObjectRouteState extends State<GetStateObjectRoute> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("子树中获取State对象"),
),
body: Center(
child: Column(
children: [
Builder(builder: (context) {
return ElevatedButton(
onPressed: () {
// 查找父级最近的Scaffold对应的ScaffoldState对象
ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>()!;
// 打开抽屉菜单
_state.openDrawer();
},
child: Text('打开抽屉菜单1'),
);
}),
],
),
),
drawer: Drawer(),
);
}
}
4.2 of静态方法的约定
一般来说,如果 StatefulWidget 的状态是私有的(不应该向外部暴露),那么我们代码中就不应该去直接获取其 State 对象;如果StatefulWidget
的状态是希望暴露出的(通常还有一些组件的操作方法),我们则可以去直接获取其State
对象。但是通过 context.findAncestorStateOfType 获取 StatefulWidget 的状态的方法是通用的,我们并不能在语法层面指定 StatefulWidget 的状态是否私有,所以在 Flutter 开发中便有了一个默认的约定:如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;如果 State不希望暴露,则不提供of
方法。这个约定在 Flutter SDK 里随处可见。所以,上面示例中的Scaffold
也提供了一个of
方法,我们其实是可以直接调用它的:
1
2
3
4
5
6
7
8
9
10
11
Builder(builder: (context) {
return ElevatedButton(
onPressed: () {
// 直接通过of静态方法来获取ScaffoldState
ScaffoldState _state = Scaffold.of(context);
// 打开抽屉菜单
_state.openDrawer();
},
child: Text('打开抽屉菜单2'),
);
}),
1
2
3
4
5
6
7
8
9
10
Builder(builder: (context) {
return ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("我是SnackBar")),
);
},
child: Text('显示SnackBar'),
);
}),
4.3 第二种办法:通过GlobalKey
4.3.1 给目标StatefulWidget添加GlobalKey
1
2
3
4
5
6
7
//定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
static GlobalKey<ScaffoldState> _globalKey = GlobalKey();
...
Scaffold(
key: _globalKey , //设置key
...
)
4.3.2 通过GlobalKey来获取State对象
1
_globalKey.currentState.openDrawer()
GlobalKey 是 Flutter 提供的一种在整个 App 中引用 element 的机制。如果一个 widget 设置了GlobalKey
,那么我们便可以通过globalKey.currentWidget
获得该 widget 对象、globalKey.currentElement
来获得 widget 对应的element对象,如果当前 widget 是StatefulWidget
,则可以通过globalKey.currentState
来获得该 widget 对应的state对象。