Flutter学习-有状态的StatefulWidget
Posted GY-93
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter学习-有状态的StatefulWidget相关的知识,希望对你有一定的参考价值。
Flutter学习-有状态的StatefulWidget
1. StatefulWidget
在开发中,某些Widget情况下我们展示的数据并不是一层不变的:
比如Flutter默认程序中的计数器案例,点击了+号按钮后,显示的数字需要+1;
比如在开发中,我们会进行下拉刷新、上拉加载更多,这时数据也会发生变化;
而StatelessWidget通常用来展示哪些数据固定不变的,如果数据会发生改变,我们使用StatefulWidget;
1.1 StatefulWidget的介绍
如果你有阅读过默认我们创建Flutter的示例程序,那么你会发现它创建的是一个StatefulWidget
-
为什么选择StatefulWidget呢?
- 因为在示例代码中,当我们点击按钮时,界面上显示的数据会发生改变;
- 这时,我们需要一个
变量
来记录当前的状态,再把这个变量显示到某个Text Widget上 - 并且每次
变量
发生改变时,我们对应的Text上显示的内容也要发生改变;
-
但是有一个问题,我之前说过定义到Widget中的数据都是不可变的,必须定义为final,为什么呢?
- 这次因为Flutter在设计的时候就决定了一旦Widget中展示的数据发生变化,就重新构建整个Widget;
- 下一个章节我会讲解Flutter的渲染原理,Flutter通过一些机制来限定定义到Widget中的成员变量必须是final的;
Flutter如何做到我们在开发中定义到Widget中的数据一定是final的呢?
我们来看下源码:
@immutable
abstract class Widget extends DiagnosticableTree {
// ...省略代码
}
- 这里有一个很关键的东西@immutable,实际上官方有对@immutable进行说明
- 来源:https://api.flutter.dev/flutter/meta/immutable-constant.html
- 说明: 被@immutable注解标明的类或者子类都必须是不可变的
- 结论:
定义到Widget中的数据一定是不可变的,需要使用final来修饰
1.2 如何存储Widget状态?
既然Widget是不可变,那么StatefulWidget如何来存储可变的状态呢?
- StatelessWidget无所谓,因为它里面的数据通常是直接定义玩后就不修改的。
- 但StatefulWidget需要有状态(可以理解成变量)的改变,这如何做到呢?
- Flutter将StatefulWidget设计成了两个类:
- 也就是你创建StatefulWidget时必须创建两个类
- 一个类继承自StatefulWidget,作为Widget树的一部分;
- 一个类继承自State,用于记录StatefulWidget会变化的状态,并且根据状态的变化,构建出新的Widget;
创建一个StatefulWidget,我们通常会按照如下格式来做:
- 当Flutter在构建Widget Tree时,会获取
State的实例
,并且它调用build方法去获取StatefulWidget希望构建的Widget; - 那么,我们就可以将需要保存的状态保存在MyState中,因为它是可变的;
class MyTestfulWidget extends StatefulWidget {
const MyTestfulWidget({Key? key}) : super(key: key);
@override
_MyTestfulWidgetState createState() => _MyTestfulWidgetState();
}
class _MyTestfulWidgetState extends State<MyTestfulWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
}
思考:为什么Flutter要这样设计呢?
这是因为在Flutter中,只要数据改变了Widget就需要重新构建(rebuild)
2. StatefulWidget案例
我们通过一个案例来练习一下StatefulWidget,还是之前的计数器案例,但是我们按照自己的方式进行一些改进。
- 因为当点击按钮时,数字会发生变化,所以我们需要使用一个StatefulWidget,所以我们需要创建两个类;
- GYHomeContentBody继承自StatefulWidget,里面需要实现createState方法;
- _GYHomeContentBodyState继承自State,里面实现build方法,并且可以定义一些成员变量
- 示例代码:
import 'package:flutter/material.dart';//导入对应的库
//main函数作为程序的入口
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(
"计数器"
),
),
body: GYHomeContent()
)
);
}
}
class GYHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return GYHomeContentBody();
}
}
// Widget是不加_: 暴露给别人使用
class GYHomeContentBody extends StatefulWidget {
const GYHomeContentBody({Key? key}) : super(key: key);
@override
_GYHomeContentBodyState createState() => _GYHomeContentBodyState();
}
/**
* 为什么Flutter在设计的时候StatefulWidget的build方法放在State中
* 1.build出来的Widget是需要依赖State中的变量(状态/数据)
* 2.在Flutter的运行过程中:
* Widget是不断的销毁和创建的
* 当我们自己的状态发生改变时, 并不希望重新状态一个新的State
*/
// State是加_: 状态这个类只是给Widget使用
class _GYHomeContentBodyState extends State<GYHomeContentBody> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
onPressed: () {
setState(() {
_count++;
});
},
child: Text("加一"),
color: Colors.pink),
RaisedButton(
onPressed: () {
setState(() {
_count--;
});
},
child: Text("减一"),
color: Colors.orange)
],
),
Text("当前计数: $_count", style: TextStyle(fontSize: 24))
],
),
);
}
}
- 按钮点击状态改变:
我们现在要监听状态的改变,当状态改变时要修改_count变量:- 但是,直接修改变量可以改变界面吗?不可以。
- 这是因为Flutter并不知道我们的数据发生了改变,需要来重新构建我们界面中的Widget
如何可以让Flutter知道我们的状态发生改变了,重新构建我们的Widget呢?
- 我们需要调用一个State中默认给我们提供的setState方法
- 可以在其中的回调函数中修改我们的变量;
onPressed: () {
setState(() {
_count++;
});
}
- StatefulWidget的传递值
- 如果我们传递一个数据,到StatefulWidget对象,那我们在State类中如何获取这个值
- 第一种:把值在传递一次
- 第二种:State类中有一个属性
widget
表示是和当前状态类绑定的Widget对象,这是Flutter帮我们绑定的 ,我们可以通过这个对象拿到当前Widget中的值
- 如果我们传递一个数据,到StatefulWidget对象,那我们在State类中如何获取这个值
class GYHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return GYHomeContentBody("您好");
}
}
// Widget是不加_: 暴露给别人使用
class GYHomeContentBody extends StatefulWidget {
final String message;
GYHomeContentBody(this.message);
@override
_GYHomeContentBodyState createState() => _GYHomeContentBodyState();
}
// State是加_: 状态这个类只是给Widget使用
class _GYHomeContentBodyState extends State<GYHomeContentBody> {
@override
Widget build(BuildContext context) {
print("message=======${this.widget.message}");
return Column();
}
}
3. StatefulWidget生命周期
3.1. 生命周期的理解
什么是生命周期呢?
- 客户端开发:ios开发中我们需要知道UIViewController从创建到销毁的整个过程,android开发中我们需要知道Activity从创建到销毁的整个过程。以便在不同的生命周期方法中完成不同的操作;
- 前端开发中:Vue、React开发中组件也都有自己的生命周期,在不同的生命周期中我们可以做不同的操作
Flutter小部件的生命周期:
- StatelessWidget可以由父Widget直接传入值,调用build方法来构建,整个过程非常简单;
- 而StatefulWidget需要通过State来管理其数据,并且还要监控状态的改变决定是否重新build整个Widget;
- 所以,我们主要讨论StatefulWidget的生命周期,也就是它从创建到销毁的整个过程;
3.2 生命周期简单版
那么StatefulWidget有哪些生命周期的回调呢?它们分别在什么情况下执行呢?
- 在下图中,灰色部分的内容是Flutter内部操作的,我们并不需要手动去设置它们
- 白色部分表示我们可以去监听到或者可以手动调用的方法;
我们知道StatefulWidget本身由两个类组成的:StatefulWidget
和State
,我们分开进行分析
首先,执行StatefulWidget中相关的方法:
- 执行StatefulWidget的构造函数(Constructor)来创建出StatefulWidget;
- 执行StatefulWidget的createState方法,来创建一个维护StatefulWidget的State对象
其次,调用createState创建State对象时,执行State类的相关方法:
- 执行State类的构造方法(Constructor)来创建State对象;
- 执行initState,我们通常会在这个方法中执行一些数据初始化的操作,或者也可能会发送网络请求;
注意:这个方法是重写父类的方法,必须调用super,因为父类中会进行一些其他操作;
并且如果你阅读源码,你会发现这里有一个注解(annotation):@mustCallSuper
@protected
@mustCallSuper
void initState() {
assert(_debugLifecycleState == _StateLifecycle.created);
}
- 执行didChangeDependencies方法,这个方法在两种情况下会调用
- 情况一:调用initState会调用;
- 情况二:从其他对象中依赖一些数据发生改变时,比如前面我们提到的InheritedWidget(这个后面会讲到);
- Flutter执行build方法,来看一下我们当前的Widget需要渲染哪些Widget;
- 当前的Widget不再使用时,会调用dispose进行销毁
- 手动调用setState方法,会根据最新的状态(数据)来重新调用build方法,构建对应的Widgets;
- 执行didUpdateWidget方法是在当父Widget触发重建(rebuild)时,系统会调用didUpdateWidget方法;
import 'package:flutter/material.dart';//导入对应的库
//main函数作为程序的入口
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(
"计数器"
),
),
body: GYHomeContent()
)
);
}
}
class GYHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return GYHomeContentBody();
}
}
// Widget是不加_: 暴露给别人使用
class GYHomeContentBody extends StatefulWidget {
GYHomeContentBody() {
print("GYHomeContentBody=======初始化方法");
}
@override
_GYHomeContentBodyState createState() {
print("GYHomeContentBody=======createState");
return _GYHomeContentBodyState();
}
}
// State是加_: 状态这个类只是给Widget使用
class _GYHomeContentBodyState extends State<GYHomeContentBody> {
_GYHomeContentBodyState() {
print("_GYHomeContentBodyState======初始化方法");
}
@override
void initState() {
print("_GYHomeContentBodyState========initState");
super.initState();
}
@override
void didUpdateWidget(GYHomeContentBody oldWidget) {
super.didUpdateWidget(oldWidget);
print("_GYHomeContentBodyState=========didUpdateWidget");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("HYHomeContentState=========didChangeDependencies方法");
}
int _count = 0;
@override
Widget build(BuildContext context) {
print("_GYHomeContentBodyState========build方法");
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
onPressed: () {
print("点击按钮事件=========");
setState(() {
_count++;
});
},
child: Text("加一"),
color: Colors.pink),
Text("当前计数: $_count", style: TextStyle(fontSize: 24))
],
),
);
}
}
运行后的结果:
3.3 生命周期复杂版
我们来学习几个前面生命周期图中提到的属性,但是没有详细讲解的
mounted
是State
内部设置的一个属性,事实上我们不了解它也可以,但是如果你想深入了解它,会对State的机制理解更加清晰;- 很多资料没有提到这个属性,但是我这里把它列出来,是内部设置的,不需要我们手动进行修改;
-
dirty state的含义是脏的State
- 它实际是通过一个Element的东西的属性来标记的;
- 将它标记为dirty会等待下一次的重绘检查,强制调用build方法来构建我们的Widget;
-
clean state的含义是干净的State
- 它表示当前build出来的Widget,下一次重绘检查时不需要重新build
-
setState()
方法的作用是,判断dirty
标记,如果该标记是true则直接返回,如果标记是false,则把标记设置为true,dirty=true
以上是关于Flutter学习-有状态的StatefulWidget的主要内容,如果未能解决你的问题,请参考以下文章
Flutter 有状态的代码问题。在 setstate 中放啥?