1 Widget 简介
在Flutter中,一切皆是Widget(组件),Widget的功能是“描述一个UI元素的配置数据”,它就是说,Widget其实并不是表示最终绘制在设备屏幕上的显示元素,而它只是描述显示元素的一个配置数据。
实际上,Flutter中真正代表屏幕上显示元素的类是 Element,也就是说Widget 只是描述 Element 的配置数据。并且一个 Widget 可以对应多个 Element,因为同一个 Widget 对象可以被添加到 UI树的不同部分,而真正渲染时,UI树的每一个 Element 节点都会对应一个 Widget 对象。
其中组件又包括无状态组件和有状态组件。
无状态组件(StatelessWidget)
无状态组件,可以理解为将外部传入的数据转化为界面展示的内容,只会渲染一次。有状态组件(StatefulWidget)
有状态组件,是定义交互逻辑和业务数据,可以理解为具有动态可交互的内容界面,会根据数据的变化进行多次渲染。
StatelessWidget 和 StatefulWidget 都是直接继承自 Widget 类,而这两个类也正是 Flutter 中非常重要的两个抽象类,它们引入了两种 Widget 模型。
2 两种Widget模型
2.1 StatelessWidget
@override
StatelessElement createElement() => new StatelessElement(this);
StatelessWidget相对比较简单,它继承自Widget类,重写了createElement()方法。
StatelessElement 间接继承自Element类,与StatelessWidget相对应(作为其配置数据)。
StatelessWidget用于不需要维护状态的场景,并且只会被渲染一次,它通常在build方法中通过嵌套其它Widget来构建UI,在构建过程中会递归的构建其嵌套的Widget。
2.2 StatefulWidget
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => new StatefulElement(this);
@protected
State createState();
}
和StatelessWidget一样,StatefulWidget也是继承自Widget类,并重写了createElement()方法,不同的是返回的Element 对象并不相同;另外StatefulWidget类中添加了一个新的接口createState()。
StatefulElement 间接继承自Element类,与StatefulWidget相对应(作为其配置数据)。StatefulElement中可能会多次调用createState()来创建状态(State)对象。
createState() 用于创建和StatefulWidget相关的状态,它在StatefulWidget的生命周期中可能会被多次调用。例如,当一个StatefulWidget同时插入到widget树的多个位置时,Flutter framework就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。
2.2.1 State
一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态,State中的保存的状态信息可以:
- 在widget 构建时可以被同步读取。
- 在widget生命周期中可以被改变,当State被改变时,可以手动调用其setState()方法通知Flutter framework状态发生改变,Flutter framework在收到消息后,会重新调用其build方法重新构建widget树,从而达到更新UI的目的。
State中有两个常用属性:
- widget,它表示与该State实例关联的widget实例,由Flutter framework动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI树上的某一个节点的widget实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果widget被修改了,Flutter framework会动态设置State.widget为新的widget实例。
- context,StatefulWidget对应的BuildContext,作用同StatelessWidget的BuildContext,表示当前widget在widget树中的上下文,每一个widget都会对应一个context对象(因为每一个widget都是widget树上的一个节点)。
3 生命周期
3.1 组件生命周期
Flutter 中说的生命周期,是独指有状态组件的生命周期,对于无状态组件生命周期只有一次 build 这个过程,也只会渲染一次。有状态组件的生命周期如下图:
Flutter 中的生命周期,包含以下几个阶段:
createState ,该函数为 StatefulWidget 中创建 State 的方法,当 StatefulWidget 被调用时会立即执行 createState 。
initState ,该函数为 State 初始化调用,因此可以在此期间执行 State 各变量的初始赋值,同时也可以在此期间与服务端交互,获取服务端数据后调用 setState 来设置 State。
didChangeDependencies ,当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个InheritedWidget,然后在之后的build() 中InheritedWidget发生了变化,那么此时InheritedWidget的子widget的didChangeDependencies()回调都会被调用。典型的场景是当系统语言Locale或应用主题改变时,Flutter framework会通知widget调用此回调。
build ,主要是返回需要渲染的 Widget ,由于 build 会被调用多次,因此在该函数中只能做返回 Widget 相关逻辑,避免因为执行多次导致状态异常。在 build 之后还有个回调
addPostFrameCallback
,在当前帧绘制完成后会回调,注册之后不能被解注册并且只会回调一次;addPostFrameCallback
是 SchedulerBinding 的方法;由于mixin WidgetsBinding on SchedulerBinding
,所以添加这个回调有两种方式:SchedulerBinding.instance.addPostFrameCallback((_) => {});
或者WidgetsBinding.instance.addPostFrameCallback((_) => {});
reassemble, 在 debug 模式下,每次热重载都会调用该函数,因此在 debug 阶段可以在此期间增加一些 debug 代码,来检查代码问题。
didUpdateWidget ,在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。正如之前所述,Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。父组件发生 build 的情况下,子组件该方法才会被调用,其次该方法调用之后一定会再调用本组件中的 build 方法。
deactivate ,在组件被移除节点后会被调用,如果该组件被移除节点,然后未被插入到其他节点时,则会继续调用 dispose 永久移除。
dispose ,永久移除组件,并释放组件资源。
Flutter 生命周期的整个过程可以分为四个阶段
- 初始化阶段:createState 和 initState
- 组件创建阶段:didChangeDependencies 和 build
- 触发组件 build:didChangeDependencies、setState 或者didUpdateWidget 都会引发的组件重新 build
- 组件销毁阶段:deactivate 和 dispose
3.2 组件首次加载过程
我们通过代码来看下组件首次加载执行的生命周期过程
在 lib 中创建 test_stateful_widget.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class TestStatefulWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
print('create state');
return TestState();
}
}
class TestState extends State<TestStatefulWidget> {
/// 定义 state [count] 计算器
int count = 1;
/// 定义 state [name] 为当前描述字符串
String name = 'test';
@override
void initState() {
print('init state');
super.initState();
}
@override
void didChangeDependencies() {
print('did change dependencies');
super.didChangeDependencies();
}
@override
void didUpdateWidget(TestStatefulWidget oldWidget) {
count++;
print('did update widget');
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print('deactivate');
super.deactivate();
}
@override
void dispose() {
print('dispose');
super.dispose();
}
@override
void reassemble() {
print('reassemble');
super.reassemble();
}
void changeName() {
setState(() {
print('set state');
this.name = 'Flutter';
});
}
@override
Widget build(BuildContext context) {
print('build');
return Column(
children: <Widget>[
FlatButton(
child: Text('$name $count'), onPressed: () => this.changeName())
],
);
}
}
在 main.dart 中加载该组件
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
theme: ThemeData(
primaryColor: Colors.amberAccent
),
home: Scaffold(
appBar: AppBar(
title: Text('生命周期测试'),
),
body: Center(
child: TestStatefulWidget(),
),
),
);
}
}
我们打开手机模拟器,然后运行该 App ,在输出控制台可以看到下面的运行打印日志信息:
I/flutter ( 7729): create state
I/flutter ( 7729): init state
I/flutter ( 7729): did change dependencies
I/flutter ( 7729): build
当我们点击 Android Studio 中的 Hot Reload 按钮(黄色小闪电 ⚡️)时控制台输出信息如下:
I/flutter ( 7729): reassemble
I/flutter ( 7729): did update widget
I/flutter ( 7729): build
3.3 触发组件再次 build
触发组件再次 build 有三种方式
- setState
- didChangeDependencies
- didUpdateWidget
setState的场景开发中你那个经常遇到,在数据状态变化时触发组件 build,在上面的代码运行后的界面中点击 test 文本按钮,然后观察控制台输出如下:
I/flutter ( 7729): set state
I/flutter ( 7729): build
didChangeDependencies场景:一般情况下我们会将一些比较基础的数据放到全局变量中,例如主题颜色、地区语言或者其他通用变量等。如果这些全局 state 发生状态变化则会触发该函数,而该函数之后就会触发 build 操作。
接下来看下didUpdateWidget 触发 build 的场景:
创建一个组件 SubStatefulWidget 继承 TestStatefulWidget
class SubStatefulWidget extends TestStatefulWidget {
@override
State<StatefulWidget> createState() {
print('sub create state');
return SubState();
}
}
class SubState extends State<SubStatefulWidget> {
@override
void initState() {
print('sub init state');
super.initState();
}
@override
void didChangeDependencies() {
print('sub did change dependencies');
super.didChangeDependencies();
}
@override
void didUpdateWidget(TestStatefulWidget oldWidget) {
print('sub did update widget');
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print('sub deactivate');
super.deactivate();
}
@override
void dispose() {
print('sub dispose');
super.dispose();
}
@override
void reassemble() {
print('sub reassemble');
super.reassemble();
}
void changeName() {
setState(() {
print('sub set state');
});
}
@override
Widget build(BuildContext context) {
print('sub build');
return Text('SubStatefulWidget');
}
}
接着在 TestStatefulWidget 加载该组件
@override
Widget build(BuildContext context) {
print('build');
return Column(
children: <Widget>[
FlatButton(
child: Text('$name $count'), onPressed: () => this.changeName()),
SubStatefulWidget()
],
);
}
重新加载 APP,观看控制台输出如下:
I/flutter ( 8464): create state
I/flutter ( 8464): init state
I/flutter ( 8464): did change dependencies
I/flutter ( 8464): build
I/flutter ( 8464): sub create state
I/flutter ( 8464): sub init state
I/flutter ( 8464): sub did change dependencies
I/flutter ( 8464): sub build
上面日志先后打印了 TestStatefulWidget 与 SubStatefulWidget 四个状态函数 createState、initState、didChangeDependencies 和 build。当我们再次点击 test 文本按钮,观察控制台输出如下:
I/flutter ( 9425): set state
I/flutter ( 9425): build
I/flutter ( 9425): sub did update widget
I/flutter ( 9425): sub build
通过上面打印的日志我们可知,父组件调用setState不仅会触发自己 build,还会引发子组件重新 build ,虽然子组件没有任何改动。
3.4 触发组件销毁
在3.3的代码的基础上,我们直接在 TestStatefulWidget 组件中注释子组件 SubStatefulWidget 的调用
@override
Widget build(BuildContext context) {
print('build');
return Column(
children: <Widget>[
FlatButton(
child: Text('$name $count'), onPressed: () => this.changeName()),
// SubStatefulWidget()
],
);
}
然后点击 Android Studio 上的 Hot Reload 按钮观察控制台输出如下信息:
I/flutter ( 9425): reassemble
I/flutter ( 9425): sub reassemble
I/flutter ( 9425): did update widget
I/flutter ( 9425): build
I/flutter ( 9425): sub deactivate
I/flutter ( 9425): sub dispose
可以看到 SubStatefulWidget 被销毁
4 App 生命周期监听
在 Flutter 中,可以利用 WidgetsBindingObserver 类来监听App 生命周期
abstract class WidgetsBindingObserver {
// 页面 pop
Future<bool> didPopRoute() => Future<bool>.value(false);
// 页面 push
Future<bool> didPushRoute(String route) => Future<bool>.value(false);
// 系统窗口相关改变回调,如旋转
void didChangeMetrics() { }
// 文本缩放系数变化
void didChangeTextScaleFactor() { }
// 系统亮度变化
void didChangePlatformBrightness() { }
// 本地化语言变化
void didChangeLocales(List<Locale> locale) { }
//App 生命周期变化
void didChangeAppLifecycleState(AppLifecycleState state) { }
// 内存警告回调
void didHaveMemoryPressure() { }
//Accessibility 相关特性回调
void didChangeAccessibilityFeatures() {}
}
其中didChangeAppLifecycleState(AppLifecycleState state)
就是用来监听App生命周期。在 didChangeAppLifecycleState 回调函数中,有一个参数类型为 AppLifecycleState 的枚举类,这个枚举类是 Flutter 对 App 生命周期状态的封装。它的常用状态包括 resumed、inactive、paused 这三个。
- resumed:可见的,并能响应用户的输入。
- inactive:处在不活动状态,无法处理用户响应。
- paused:不可见并不能响应用户的输入,但是在后台继续活动中。
当切换前、后台时,App 状态如下:
- 从后台切入前台: AppLifecycleState.paused -> AppLifecycleState.inactive -> AppLifecycleState.resumed
- 从前台退回后台:AppLifecycleState.resumed -> AppLifecycleState.inactive -> AppLifecycleState.paused
5 帧绘制回调
5.1 addPostFrameCallback
单次 Frame 绘制回调,会在当前 Frame 绘制完成后进行回调,并且只会回调一次,如果要再次监听则需要再设置一次。
WidgetsBinding.instance.addPostFrameCallback((_){
print(" 单次 Frame 绘制回调 ");
});
5.2 addPersistentFrameCallback
实时 Frame 绘制回调,会在每次绘制 Frame 结束后进行回调,可以用做 FPS 监测。
WidgetsBinding.instance.addPersistentFrameCallback((_){
print(" 实时 Frame 绘制回调");
});
6 总结
- Flutter 中 Widget 分为两种:StatelessWidget 和 StatefulWidget
- Flutter 生命周期的整个过程可以分为四个阶段:初始化、组件创建、触发组件 build、组件销毁
- StatelessWidget 用于不需要维护状态的场景,只会 build 一次
- StatefulWidget 会被多次触发 build 函数,触发函数是setState、didChangeDependencies、didUpdateWidget
- 父组件调用setState不仅会触发自己 build,还会引发子组件重新 build ,虽然子组件没有任何改动
- 可以利用 WidgetsBindingObserver 类来监听App 生命周期
- 帧绘制回调有单次 Frame 绘制回调(addPostFrameCallback)和实时 Frame 绘制回调(addPersistentFrameCallback)