Flutter第四章(StatelessWidget,State
版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!
情感语录:没死,就别把自己当成废物;你本是人生的作者,何必把剧本写得苦不堪言。
欢迎来到本章节,上一章节我们讲了8个常用组件 还记得吗?知识点回顾 戳这里 Fultter基础第三章
本章节主要介绍StatelessWidget
,StatefulWidget
组件,这俩组件在第一章就有提到,且在前面章节练习中也有运用到,这里为什么要单独开一篇文章来介绍呢?当然是因为他两太重要啦!!! 毫不夸张的说在Flutter面向Widget编程中StatefulWidget组件运用的适不适当将直接影响你的代码是否需要重构。
本章简要:
1、StatelessWidget
组件
2、StatefulWidget
组件
一、Flutter中怎样自定义组件
自定义组件如同Android原生开发中一样继承View或者ViewGroup类去实现你自己想要的界面,在 Flutter 中自定义组件其实也是一个类,这个类需要继承 StatelessWidget 或 StatefulWidget。
二、StatelessWidget和StatefulWidget区别
StatelessWidget 是无状态组件,状态不可变的 widget, 它没有要管理的内部状态。
StatefulWidget 是有状态组件,持有的状态可能在 widget 生命周期改变。
粗暴的讲:如果我们想改变页面中数据的话这个时候就需要用到 StatefulWidget。StatefulWidget 与 StatelessWidget 唯一的区别就在这里,如果只是渲染视图,状态变更不需要反映到模板中,则使用 StatelessWidget 即可,否则使用 StatefulWidget。
StatelessWidget和StatefulWidget的常用子类都哪些呢?
StatelessWidget:例如 Icon、 IconButton, 和Text 都是无状态widget, 他们都是 StatelessWidget的子类。
StatefulWidget :是可变状态的widget。 使用setState
方法管理StatefulWidget的状态的改变。调用setState
告诉Flutter,某个状态发生了变化,Flutter会重新运行build方法,渲染视图。例如: Checkbox, Radio, Slider, InkWell, Form, and TextField 他们都是 StatefulWidget的子类。
三、StatelessWidget 的生命周期
StatelessWidget 的生命周期只有一个那就是:build
。它是用来创建 Widget 的,但因为 build 在每次界面刷新的时候都会调用,所以最好不要在 build 里写业务逻辑。下面我们来看下模板,我使用的Android studio 直接输入stless
即可生成。
class StatelessDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}
四、StatefulWidget 的生命周期
在Android studio 直接输入stful
即可生成StatefulWidget的模板代码如下:
class StatefulDemo extends StatefulWidget {
//@override
// _StatefulDemoState createState() => _StatefulDemoState();
//把箭头函数改成如下也可
@override
_StatefulDemoState createState(){
return _StatefulDemoState();
}
}
class _StatefulDemoState extends State<StatefulDemo> {
@override
Widget build(BuildContext context) {
return Container();
}
}
下图是 State 的生命周期图。
Statefullwidget生命周期.png1、首先Framework 会通过调用 StatefulWidget.createState()
来创建一个 State。
2、新创建的 State 会和一个 BuildContext 产生关联,之后 initState()
函数将会被调用。通常,我们可以重写这个函数,进行初始化操作。
3、在 initState() 调用结束后,didChangeDependencies()
这个函数会被调用。事实上,当 State 对象的依赖关系发生变化时,这个函数总会被 Framework 调用。
4、经过以上步骤,系统认为一个 State 已经准备好了,就会调用build()
来构建视图。我们需要在这个函数中,返回一个我们想要的 Widget。
5、当 State 被暂时从视图树中移除时,deactivate()
这个函数会被调用。页面切换时,也会调用它,因为此时 State 在视图树中的位置发生了变化,需要先暂时移除后添加。提示:
重写的时候必须要调用 super.deactivate()
。
6、当 State 被永久的从视图树中移除,Framework 会调用dispose()
函数。在销毁前触发,我们可以在这里进行最终的资源释放。在调用这个函数之前,总会先调用deactivate()
。提示:
,重写的时候必须要调用super.dispose()
。
7、reassemble()
在release中永远不会被触发。
8、didUpdateWidget(covariant T oldWidget)
当 widget 的配置发生变化时,会调用这个函数。比如,Hot-reload 的时候就会调用这个函数。这个函数调用后,会调用 build()。
9、当需要更新 State 的视图时,需要手动调用setState()
这个函数,它会触发 build() 重新渲染视图。
除开Framework帮我们创建State时触发的createState()和需要手动触发的setState()方法外,其他大致如下:
// 初始化调用
@override
void initState() {
super.initState();
}
// State对象依赖发生变化调用;系统语言、主题修改,也会通知调用
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
// 热重载会被调用,在release下永远不会被调用
@override
void reassemble() {
super.reassemble();
}
// 新旧Widget的key、runtimeType不变时调用。也就是Widget.canUpdate=>true
@override
void didUpdateWidget(CouterWidgetState oldWidget) {
super.didUpdateWidget(oldWidget);
}
// State在树中移除调用
@override
void deactivate() {
super.deactivate();
}
// State在树中永久移除调用,相当于释放
@override
void dispose() {
super.dispose();
}
// 用于子树的渲染
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
),
);
}
可以看出StatefulWidget相比StatelessWidget复杂的多,而似乎StatelessWidget能做的StatefulWidget都能做,那开发中是不是可以直接用StatefulWidget来全部代替StatelessWidget呢?当然是不可以的,这也是开篇为什么说这是衡量你代码是否需要重构的标准之一;大量的使用StatefulWidget会严重的影响你的性能问题。因为StatefulWiget状态改变会导致子widget都重建,滥用的话不仅会导致对象冗余创建,也有可能会严重卡顿。
五、结合案列分析
假设你有如下一个Widget树,紫色表示的是一个将会被改变的Widget。如果按照这样的布局结构,那么每一次紫色的叶子节点发生变化并重建,它的四个兄弟节点也会重新创建:
组件树分析1_20190819113130.png如有如下一个界面刷新效果,每次点击测试按钮,AAA 后面的计数器就加1,而希望其他的兄弟节点不被重新构建。
组件树刷新案例1_20190819135340.png我们先来看这样一段代码:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text("呆萌"),
),
body: new Column(
children: <Widget>[MyStatefulWidget()],
),
));
}
}
class MyStatefulWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
print("--------> MyStatefulWidget: build");
return MyState();
}
}
class MyState extends State<MyStatefulWidget> {
int index = 0;
@override
Widget build(BuildContext context) {
print("-------->MyState build");
return Container(
color: Colors.red,
child: Column(
children: <Widget>[
MyStatelessWidget("AAA", "AAA:" + index.toString()),
SizedBox(
height: 30,
),
MyStatelessWidget("BBB", "BBB"),
SizedBox(
height: 30,
),
MyStatelessWidget("CCC", "CCC"),
SizedBox(
height: 30,
),
MyStatelessWidget("DDD", "DDD"),
SizedBox(
height: 30,
),
GestureDetector(
onTap: () {
setState(() {
index++;
});
},
child: TextWidget("点击测试"),
)
],
),
);
}
}
class MyStatelessWidget extends StatelessWidget {
final String _text;
final String _name;
MyStatelessWidget(this._name, this._text) {
print("---------> MyStatelessWidget:" + _name + " 构造");
}
@override
Widget build(BuildContext context) {
print("---------> MyStatelessWidget:" + _name + " build");
if (_name == "DDD") {
for (int i = 0; i < 10; i++) {
print("for:" + i.toString());
}
print("这是一个耗时操作");
}
return TextWidget(_text);
}
}
class TextWidget extends StatelessWidget {
final String _text;
TextWidget(this._text);
@override
Widget build(BuildContext context) {
return Text(
_text,
style: TextStyle(fontSize: 20, color: Colors.deepPurple, wordSpacing: 10),
);
}
}
这段代码不难理解首先在Column的子元素中添加了一个StatefulWidget 的组件来做4个 MyStatelessWidget
叶子节点的根节点,并在DDD
的叶子节点中添加一个耗时操作。 当触发点击事件onTap
时调用setState
来企图改变计数器的值。现在我们运行并点击下测试按钮,看下效果:
I/flutter ( 5274): -------->MyState build
I/flutter ( 5274): ---------> MyStatelessWidget:AAA 构造
I/flutter ( 5274): ---------> MyStatelessWidget:BBB 构造
I/flutter ( 5274): ---------> MyStatelessWidget:CCC 构造
I/flutter ( 5274): ---------> MyStatelessWidget:DDD 构造
I/flutter ( 5274): ---------> MyStatelessWidget:AAA build
I/flutter ( 5274): ---------> MyStatelessWidget:BBB build
I/flutter ( 5274): ---------> MyStatelessWidget:CCC build
I/flutter ( 5274): ---------> MyStatelessWidget:DDD build
I/flutter ( 5274): 模拟一个耗时操作开始
I/flutter ( 5274): for:0
I/flutter ( 5274): for:1
I/flutter ( 5274): for:2
I/flutter ( 5274): for:3
I/flutter ( 5274): for:4
I/flutter ( 5274): for:5
I/flutter ( 5274): for:6
I/flutter ( 5274): for:7
I/flutter ( 5274): for:8
I/flutter ( 5274): for:9
I/flutter ( 5274): 模拟一个耗时操作结束
看到这样的输出结果,顿时就想一句卧槽破口而出了,虽然从界面上看效果是实现了,但我想要的是点击测试按钮时,只需刷新AAA
即可,然而根节点下的所有兄弟节点都被刷新构建了,试想DDD
或者其他兄弟节点中假如都有非常耗时的操作,毫无疑问这对你应用就是一场毁灭性的灾难。
上面问题怎样优化呢?其实只需要将变化的节点封装到一个更小的分支当中,使得它的兄弟节点尽可能的少,便可解决该问题。
组件树分析2_20190819113400.png下面我们来将代码简单的优化下,先将MyStatefulWidget
中要刷新的部分抽离出来,然后将MyStatelessWidget
提升到StatelessWidget节点下:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text("呆萌"),
),
body: new Column(
children: <Widget>[
SizedBox(
height: 30,
),
MyStatelessWidget("BBB", "BBB"),
SizedBox(
height: 30,
),
MyStatelessWidget("CCC", "CCC"),
SizedBox(
height: 30,
),
MyStatelessWidget("DDD", "DDD"),
SizedBox(
height: 30,
),
MyStatefulWidget(),
],
),
));
}
}
class MyStatefulWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
print("--------> MyStatefulWidget: build");
return MyState();
}
}
class MyState extends State<MyStatefulWidget> {
int index = 0;
@override
Widget build(BuildContext context) {
print("-------->MyState build");
return Container(
color: Colors.red,
child: Column(
children: <Widget>[
MyStatelessWidget("AAA", "AAA:" + index.toString()),
GestureDetector(
onTap: () {
setState(() {
index++;
});
},
child: TextWidget("点击测试"),
)
],
),
);
}
}
class MyStatelessWidget extends StatelessWidget {
final String _text;
final String _name;
MyStatelessWidget(this._name, this._text) {
print("---------> MyStatelessWidget:" + _name + " 构造");
}
@override
Widget build(BuildContext context) {
print("---------> MyStatelessWidget:" + _name + " build");
if (_name == "DDD") {
print("模拟一个耗时操作开始");
for (int i = 0; i < 10; i++) {
print("for:" + i.toString());
}
print("模拟一个耗时操作结束");
}
return TextWidget(_text);
}
}
class TextWidget extends StatelessWidget {
final String _text;
TextWidget(this._text);
@override
Widget build(BuildContext context) {
return Text(
_text,
style: TextStyle(fontSize: 20, color: Colors.deepPurple, wordSpacing: 10),
);
}
}
再次运行看看效果。
I/flutter ( 5274): -------->MyState build
I/flutter ( 5274): ---------> MyStatelessWidget:AAA 构造
I/flutter ( 5274): ---------> MyStatelessWidget:AAA build
可以看出AAA
的重绘不会再使得BBB,CCC....等
重新绘制。
六、避坑要点
在Flutter中编程一切都是Widget,对于StatefulWidget
和StatelessWidget
的选用拿不定主意,记住以下几点避免踩坑:
① 在自定义Widget时优先使用 StatelessWidget。
② 有状态需要改变的尽量在叶子节点才使用 StatefulWidget。
③ 含有大量子节点和兄弟节点慎用 StatefulWidget。
④ 将会调用到 setState() 的代码尽可能的和要更新的视图封装在一个尽可能小的模块里,减少它的子节点、兄弟节点。
好了本章节就此结束,又到了说再见的时候了,如果你喜欢请留下你的小红星,你们的支持才是创作的动力,如有错误,请热心的你留言指正, 谢谢大家观看,下章再会 O(∩_∩)O