Flutter - StateWidget与生命周期
# 一、程序入口
一般情况下,Flutter 的主入口是 main.dart
。
# 1、界面的本质(Widget)
在 main 函数中通过 runApp 函数启动一个 Flutter 界面,而 runApp(Widget app)
函数接收的参数 Widget app
就是界面了,即界面是一个 Widget
:
main() {
runApp(Center(
child: Text("Hello world",
textDirection: TextDirection.ltr,
style: TextStyle(fontSize: 30, color: Colors.orange)),
));
}
在 Flutter 中,所有的界面、控件都是
Widget
# 2、MaterialApp
为了快速开发一个标准 material
的界面,Flutter 提供了 MaterialApp
这个 Widget:
import 'package:flutter/material.dart';
main() {
// 1. runApp 函数
runApp(MaterialApp(
// debugShowCheckedModeBanner: false, // 控制界面右上角是否显示`debug`提示
home: Scaffold(
appBar: AppBar(
title: Text("第一个Flutter程序"),
),
body: Center(
child: Text(
"Hello World",
textDirection: TextDirection.ltr,
style: TextStyle(fontSize: 30, color: Colors.orange),
),
),
),
));
}
Scaffold
也是Widget
,翻译为脚手架,可以帮助开发者搭建出带AppBar
的界面
# 二、State Widget
万物基于 Widget 的 Flutter 把 Widget 分为两类,分别是:
StatelessWidget
: 无状态的 Widget,内容是确定没有状态(data)的改变StatefulWidget
: 有状态的 Widget,在运行过程中有一些状态(data)需要改变
# 1、StatelessWidget
上面的 demo 中,runApp(Widget app)
、MaterialApp({this.home})
、Scaffold({this.body})
这 3 个方法接收的主要参数都是 Widget 类型,并且不涉及状态的保存,因此可以把它们抽成对应的 3 个 StatelessWidget
来使用,这样就可以有效的避免 嵌套地狱
的发生:
这种抽取出一个个 Widget 的方式,类似于大前端中的组件化开发
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第一个Flutter程序"),
),
body: ContentBody(),
);
}
}
class ContentBody extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Text(
"Hello World",
textDirection: TextDirection.ltr,
style: TextStyle(fontSize: 30, color: Colors.orange),
),
);
}
}
AndroidStudio 中的 State Widget 快速创建模板:
- stless:输入 stless 直接生成 StatelessWidget
- stful:输入 stful 直接生成 StatefulWidget
# 2、StatefulWidget
Flutter 是声明式开发,即一旦状态发生变化,界面会自动改变,这里说的状态就是数据。StatefulWidget 就是可以有状态的 Widget,一个简单的例子:写一个同意协议控件:
/// flag:状态
/// Stateful不能定义状态 -> 创建一个单独的State类,这个State类负责维护状态
class ContentBody extends StatefulWidget {
State<StatefulWidget> createState() {
return ContentBodyState();
}
}
/// 使用State类来保存状态
class ContentBodyState extends State<ContentBody> {
var flag = true;
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Checkbox(
value: flag,
onChanged: (value) {
/// flag = value; // 无效代码,不会刷新界面
/// 必须使用 setState() 方法强制界面刷新
setState(() {
flag = value;
});
},
),
Text("同意协议", style: TextStyle(fontSize: 20)),
],
),
);
}
}
注意:
- Flutter 中的状态(State)和 React 中的状态概念一致。
- 单单修改数据是不行的,还必须调用
setState()
通知界面在下一帧重新绘制才行。
# 3、为什么 State Widget 本身不能定义状态?
无论 StatelessWidget
还是 StatefulWidget
,其父类都是 Widget
,来看看 Widget 的定义:
abstract class Widget extends DiagnosticableTree {
...
}
Widget 有 @immutable
注解,就意味着 Widget 的所有子类即使有成员属性,也一定是使用 final 修饰的,试问 final 变量会有变化吗?
/// 所有的Widget类中都不能定义状态,类成员属性必须是final
class ContentBody extends StatelessWidget {
// IDE报错:This class (or a class that this class inherits from) is marked as '@immutable', but one of more of its instance fields aren't final: ContentBody.flag
// 错误代码
// @immutable 注释过的类,成员属性必须是final的
var flag = true;
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(value: flag, onChanged: (value) => flag = value),
Text("同意协议", style: TextStyle(fontSize: 20)),
],
),
);
}
}
# 三、综合案例
# 1、StatelessWidget 综合案例(商品列表)
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("商品列表"),
),
body: HomeContent(),
);
}
}
class HomeContent extends StatelessWidget {
Widget build(BuildContext context) {
// Column 会越界relayoutBoundary,改成ListView就好了
return ListView(
children: [
HomeProductItem("Apple1", "Macbook1",
"https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
SizedBox(height: 6),
HomeProductItem("Apple2", "Macbook2",
"https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
SizedBox(height: 6),
HomeProductItem("Apple3", "Macbook3",
"https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg"),
],
);
}
}
class HomeProductItem extends StatelessWidget {
final String title;
final String desc;
final String imageURL;
final style1 = TextStyle(fontSize: 25, color: Colors.orange);
final style2 = TextStyle(fontSize: 20, color: Colors.green);
HomeProductItem(this.title, this.desc, this.imageURL);
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(
width: 5, // 设置边框的宽度
color: Colors.pink, // 设置边框的颜色
),
),
child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// crossAxisAlignment: CrossAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.end,
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Text(title, style: style1, textAlign: TextAlign.center),
Text(title, style: style1),
SizedBox(height: 8),
Text(desc, style: style2),
SizedBox(height: 8),
Image.network(imageURL),
],
),
);
}
}
# 2、StatefulWidget 综合案例(计数器)
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("title")),
body: HomeContent("hello world"),
);
}
}
/// 为什么Flutter在设计的时候StatefulWidget的build方法放在State中
/// 1. build出来的Widget是需要依赖State中的变量(状态/数据)
/// 2. 在Flutter的运行过程中:
/// Widget是不断的销毁和创建的
/// 当我们自己的状态发生改变时,并不希望重新创建一个新的State
class HomeContent extends StatefulWidget {
final String message;
HomeContent(this.message);
_HomeContentState createState() => _HomeContentState();
}
class _HomeContentState extends State<HomeContent> {
int _counter = 0;
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_getButtons(),
Text("当前计数:$_counter", style: TextStyle(fontSize: 25)),
Text("传递的信息:${widget.message}")
],
),
);
}
Widget _getButtons() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
child: Text("+", style: TextStyle(fontSize: 20)),
style: TextButton.styleFrom(backgroundColor: Colors.pink),
onPressed: () {
setState(() {
_counter++;
});
},
),
ElevatedButton(
child: Text("-", style: TextStyle(fontSize: 20)),
style: TextButton.styleFrom(backgroundColor: Colors.purple),
onPressed: () {
setState(() {
_counter--;
});
},
),
],
);
}
}
# 四、生命周期
# 1、StatelessWidget 的生命周期
因为 StatelessWidget 没有状态,所以它的生命周期很简单:先调用 构造函数
,再调用 build()
,没了:
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("title")),
body: HomeContent("hello world"),
);
}
}
// StatelessWidget 的生命周期
class HomeContent extends StatelessWidget {
final String message;
HomeContent(this.message) {
print("构造函数被调用");
}
Widget build(BuildContext context) {
print("调用build方法");
return Text(message);
}
}
日志输出:
I/flutter ( 3092): 构造函数被调用
I/flutter ( 3092): 调用build方法
# 2、StatefulWidget 的生命周期
方法 | 说明 |
---|---|
createState | Framework 会通过调用 StatefulWidget.createState 来创建一个 State。 |
initState | 新创建的 State 会和一个 BuildContext 产生关联,此时认为 State 已经被安装好了,initState 函数将会被调用。通常,我们可以重写这个函数,进行初始化操作。 |
didChangeDependencies | 在 initState 调用结束后,这个函数会被调用。事实上,当 State 对象的依赖关系发生变化时,这个函数总会被 Framework 调用。 |
build | 经过以上步骤,系统认为一个 State 已经准备好了,就会调用 build 来构建视图。我们需要在这个函数中返回一个 Widget。 |
deactivate | deactivate 当 State 被暂时从视图树中移除时,会调用这个函数。页面切换时,也会调用它,因为此时 State 在视图树中的位置发生了变化,需要先暂时移除后添加。 |
dispose | 当 State 被永久地从视图树中移除时,Framework 会调用该函数。在销毁前触发,我们可以在这里进行最终的资源释放。在调用这个函数之前,总会先调用 deactivate 函数。 |
didUpdateWidget | 当 Widget 的配置发生变化时,会调用这个函数。比如,热重载的时候就会调用这个函数。调用这个函数后,会调用 build 函数。 |
setState | 当需要更新 State 的视图时,需要手动调用这个函数,它会触发 build 函数。 |
// StatefulWidget 的生命周期
class HomeContent extends StatefulWidget {
HomeContent(String message) {
print("1. 调用 HomeContent 的 constructor 方法");
}
_HomeContentState createState() {
print("2. 调用 HomeContent 的 createState 方法");
return _HomeContentState();
}
}
class _HomeContentState extends State<HomeContent> {
_HomeContentState() {
print("3. 调用 _HomeContentState 的 constructor 方法");
}
void initState() {
// 注意:这里必须调用super(@mustCallSuper)
super.initState();
print("4. 调用 _HomeContentState 的 initState 方法");
}
void didChangeDependencies() {
super.didChangeDependencies();
print("调用 _HomeContentState 的 didChangeDependencies 方法");
}
void didUpdateWidget(covariant HomeContent oldWidget) {
super.didUpdateWidget(oldWidget);
print("调用 _HomeContentState 的 didUpdateWidget 方法");
}
Widget build(BuildContext context) {
print("5. 调用 _HomeContentState 的 build 方法");
return Container();
}
void dispose() {
super.dispose();
print("6. 调用 _HomeContentState 的 dispose 方法");
}
}
日志结果:
// 界面初始化
I/flutter ( 3092): 1. 调用 HomeContent 的 constructor 方法
I/flutter ( 3092): 2. 调用 HomeContent 的 createState 方法
I/flutter ( 3092): 3. 调用 _HomeContentState 的 constructor 方法
I/flutter ( 3092): 4. 调用 _HomeContentState 的 initState 方法
I/flutter ( 3092): 调用 _HomeContentState 的 didChangeDependencies 方法
I/flutter ( 3092): 5. 调用 _HomeContentState 的 build 方法
// 点击计数按钮
I/flutter ( 3092): 5. 调用 _HomeContentState 的 build 方法
没有打印 dispose()中的日志,是因为这里 Demo 中没有将 Widget 移除
- 01
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 02
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21
- 03
- Flutter - 轻松实现PageView卡片偏移效果09-08