本篇主要对StatelessWidget、StatefulWidget做一个简单的介绍,并实际运用来进行一些简单界面的构建
一、StatelessWidget
StatelessWidget子类代码块生成快捷键: stless
上篇已经简单介绍了StatelessWidget的基本使用,接下来简单介绍如何使用StatelessWidget创建一个商品列表,效果如下:
- StatelessWidget参数传递
与Dart语法一致,创建属性(final修饰),实现初始化方法,进行赋值
调用:class YWHomeProduct extends StatelessWidget { final String title; final String desc; final String imageUrl; YWHomeProduct(this.title, this.desc, this.imageUrl); @override Widget build(BuildContext context) { return Column(children: [Text(title), Text(desc), Image.network(imageUrl)]); } }
YWHomeProduct("瓜子", "焦糖瓜子,香甜可口", "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.yzcdn.cn%2Fupload_files%2F2018%2F08%2F21%2FFiLdNHrxKMohbPJ2QbtOTo9MhIMm.jpg%3FimageView2%2F2%2Fw%2F580%2Fh%2F580%2Fq%2F75%2Fformat%2Fjpg&refer=http%3A%2F%2Fimg.yzcdn.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632121924&t=b37df3a44a89fc3b768c0f3f69f94f2a")
- 竖向列表
- column: 竖向布局多个控件,使用column布局,当竖向控件高度超出屏幕高度后,会显示条纹遮挡
- ListView: 竖向可滚动控件
对象创建:
函数内部可以创建局部变量,减小语句内部代码量,但是每次调用函数都会创建
在类的内部创建变量,则只会在对象实例化时创建一次
在类的外部创建变量,则在整个项目生命周期中都会存在-
创建控件之间的边距
可以通过SizedBox控件来实现,横向距离width,竖向距离heightSizedBox(height: 8)
-
边框创建
将某个控件放置到Container中,可以用Container来设置边框(decoration)以及内边距(padding)等return Container( padding: EdgeInsets.all(8), decoration: BoxDecoration(border: Border.all(color: Colors.purple, width: 8)), child: Column(children: [ Text(title, style: titleStyle), SizedBox(height: 8), Text(desc, style: descStyle), SizedBox(height: 8), Image.network(imageUrl) ]));
问题:BoxDecoration的主要用处?
用于Widget的装饰color 颜色背景 Color类型 image 图片背景 DecorationImage类型 border 边界 BoxBorder类型 borderRadius 圆角边界半径 BorderRadiusGeometry类型 boxShadow 阴影 List
类型 gradient 渐变色 Gradient类型 backgroundBlendMode 背景混合模式 BlendMode类型 shape 形状 BoxShape类型 -
控制column竖向布局控件位置
控件分为主轴(竖向)和交叉轴(横向)
主轴使用MainAxisAlignment控制子控件位置
交叉轴使用CrossAxisAlignment控制子控件位置
通过Flex可以决定主轴和交叉轴Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: titleStyle), SizedBox(height: 8), Text(desc, style: descStyle), SizedBox(height: 8), Image.network(imageUrl) ])
下面是完整的代码:
import 'package:flutter/material.dart';
main(List args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(debugShowCheckedModeBanner: false, home: YWHomePage());
}
}
class YWHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("购物车")), body: YWBodyContent());
}
}
class YWBodyContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(children: [
YWHomeProduct("瓜子", "焦糖瓜子,香甜可口",
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.yzcdn.cn%2Fupload_files%2F2018%2F08%2F21%2FFiLdNHrxKMohbPJ2QbtOTo9MhIMm.jpg%3FimageView2%2F2%2Fw%2F580%2Fh%2F580%2Fq%2F75%2Fformat%2Fjpg&refer=http%3A%2F%2Fimg.yzcdn.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632121924&t=b37df3a44a89fc3b768c0f3f69f94f2a"),
YWHomeProduct("花生", "盐水花生,补气养血",
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fi2.chuimg.com%2F25d9caf08b6811e6a9a10242ac110002_1171w_801h.jpg%3FimageView2%2F2%2Fw%2F660%2Finterlace%2F1%2Fq%2F90&refer=http%3A%2F%2Fi2.chuimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632122384&t=f3859622328496babe08d62ffca276ba"),
YWHomeProduct("八宝粥", "居家旅行,出门良品",
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcp2.douguo.net%2Fupload%2Fdish%2F7%2F3%2F3%2F600_7355c671e3bf52100c61e043c6110ae3.jpg&refer=http%3A%2F%2Fcp2.douguo.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632122038&t=cedcc17ba9ed999844c9c3c1bedf2ded")
]);
}
}
class YWHomeProduct extends StatelessWidget {
final String title;
final String desc;
final String imageUrl;
final titleStyle = TextStyle(fontSize: 20, color: Colors.orange);
final descStyle = TextStyle(fontSize: 15, color: Colors.blue);
YWHomeProduct(this.title, this.desc, this.imageUrl);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8),
decoration:
BoxDecoration(border: Border.all(color: Colors.purple, width: 8)),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(title, style: titleStyle),
SizedBox(height: 8),
Text(desc, style: descStyle),
SizedBox(height: 8),
Image.network(imageUrl)
]));
}
}
二、StatefulWidget
- StatefulWidget用法
有状态需要改变的时候要使用StatefulWidget
- StatefulWidget内部有一个抽象方法createState,此方法返回一个State类,继承自StatefulWidget的类需要实现此方法
- 在自定义的State子类中需要实现build方法生成一个控件
- StatefulWidget内部不可直接定义状态,但是可以在自定义的State子类中可以定义状态
问题:为什么Flutter在设计的时候,StatefulWidget的build方法放在State中?
- build出来的widget是需要依赖State中的变量(状态/数据)
- 在Flutter的运行过程中,widget是不断销毁和创建的,当我们的状态发生改变时,并不希望重新创建一个新的State
-
使用StatefulWidget构建一个计数器
效果图如下:
其中按钮可以使用ElevatedButton(RaisedButton已废弃)
使用Column构建竖向组件,Row构建横向组件
import 'package:flutter/material.dart';
main(List args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(debugShowCheckedModeBanner: false, home: YWHomePage());
}
}
class YWHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("购物车")), body: YWHomeContent());
}
}
class YWHomeContent extends StatefulWidget {
@override
_YWHomeContentState createState() => _YWHomeContentState();
}
class _YWHomeContentState extends State {
int number = 0;
@override
Widget build(BuildContext context) {
return Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
getButtons(),
Text("当前计数:$number", style: TextStyle(fontSize: 20))
]));
}
Widget getButtons() {
return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
ElevatedButton(
child: Text("+", style: TextStyle(fontSize: 20)),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.pink)),
onPressed: () {
setState(() {
number++;
});
}),
ElevatedButton(
child: Text("-", style: TextStyle(fontSize: 20)),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.purple)),
onPressed: () {
setState(() {
if (number > 0) {
number--;
}
});
})
]);
}
}
问题:数值改变能放在setState外面吗?
setState会触发widget build方法重新进行渲染
- StatefulWidget传值
父类给子类传值,传值方式与StatelessWdight语法一致,创建属性(final修饰),实现对应初始化方法,进行赋值,将参数传递给Widget子类
然后在State类中通过this.widget来实现取值class YWHomeContent extends StatefulWidget { final String message; YWHomeContent(this.message); @override _YWHomeContentState createState() =>_YWHomeContentState(); } class _YWHomeContentState extends State
{ int number = 0; @override Widget build(BuildContext context) { return Text("${this.widget.message}"); } }
这也就是为什么State类定义的泛型需要跟之前创建的StatefulWidget子类保持一致的原因
三、StatelessWidget、StatefulWidget生命周期
- 生命周期的作用(意义)
- 初始化一些数据、状态、变量
- 发送网络请求时机
- 进行一些事件的监听
- 管理内存(手动销毁)
- StatelessWidget生命周期
这里比较简单,只有两个生命周期
- 构造函数被调用
- 调用build方法
class TestWidget extends StatelessWidget {
TestWidget() {
print("构造函数被创建");
}
@override
Widget build(BuildContext context) {
print("build方法被调用");
return Container();
}
}
-
StatefulWidget生命周期
- 第一步:调用StatefulWidget子类的构造方法
- 第二步:调用StatefulWidget子类的createState方法
- 第三步:调用State子类的构造方法
- 第四步 调用State子类的initState方法
用于状态的初始化,会在State类完成构造之后,build方法执行之前,进行执行
initState方法使用@mustCallSuper标记,必须调用父类initState方法 - 第五步:执行State子类的build方法
- 最后,当前的Widget子类不再使用时,会执行State子类的dispose方法,进行内存的释放
此外:
- didChangeDependencies方法 在initState方法执行后,或者从其他对象中依赖一些数据发生改变时,比如inheritedWidget,会执行此方法
- didUpdateWidget方法 当重新创建Widget子类(rebuild),会触发state子类的didUpdateWidget方法
代码如下所示:
class YWHomeContent extends StatefulWidget {
YWHomeContent() {
print("1.调用YWHomeContent的初始化方法");
}
@override
_YWHomeContentState createState() {
print("2.调用YWHomeContent的createState方法");
return _YWHomeContentState();
}
}
class _YWHomeContentState extends State {
_YWHomeContentState() {
print("3.调用_YWHomeContentState的初始化方法");
}
@override
void initState() {
super.initState();
print("4.调用_YWHomeContentState的initState方法");
}
@override
void didChangeDependencies() {
print("调用_YWHomeContentState的didChangeDependencies方法");
super.didChangeDependencies();
}
@override
void didUpdateWidget(covariant YWHomeContent oldWidget) {
print("调用_YWHomeContentState的didUpdateWidget方法");
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
print("5.调用_YWHomeContentState的build方法");
return Text("Hello World");
}
@override
void dispose() {
super.dispose();
print("6.调用_YWHomeContentState的dispose方法");
}
}
- setState方法会在内部触发重新运行build方法,根据最新的状态返回新的控件,所以状态的修改需要在setState方法中进行
- StatefulWidget生命周期的复杂版
在StatefulWidget普通生命周期的基础上,还有一些复杂的过程未提及到
- mounted
- dirty state & clean state
疑问:意义及用法?
5.生命周期扩展
createState
当构建一个 StatefulWidget 会立即调用该方法
@override
_MyStatefulPageState createState() {
print("lxf -- createState");
return _MyStatefulPageState();
}
initState
在 Widget 被创建出来并插入到树中时被调用的方法,只会被调用一次,等价于安卓的 onCreate() 或 iOS 中的 viewDidLoad(),所以在此时视图还未被渲染,但是 Widget 已经被插入到树中,一般用于执行一些初始化操作
@override
void initState() {
super.initState();
print("lxf -- initState");
// 初始化操作
...
}
mounted
所有的 Widget 都拥有这个属性,当 buildContext 被分配且当前在树中时,该值为 true,直到调用 dispose 时重置为 false
addPostFrameCallback
单次 Frame(帧) 绘制回调,在当前帧绘制完成后进行回调,用于在 Widget 渲染完毕之后做一些操作,该回调只会进行一次,如果需要再次监听则需要再次设置。
@override
void initState() {
super.initState();
print("lxf -- initState");
// 单次帧绘制回调(只会回调一次,如果要再次监听需要再设置)
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
print("lxf -- addPostFrameCallback");
});
}
如果希望在实时绘制帧时进行回调,则使用 addPersistentFrameCallback
// 实时帧绘制回调(每次绘制帧结束后都会回调)
WidgetsBinding.instance?.addPersistentFrameCallback((timeStamp) {
print("lxf -- addPersistentFrameCallback");
});
didChangeDependencies
在 initState() 方法被执行后立刻被调用,之后当依赖的 InheritedWidget 发生变化时,框架将会再次调用该方法将变化通知给当前对象。
build
在 didChangeDependencies 之后被调用,在该方法中会将 Widget 进行渲染,且由于再次渲染的操作是廉价的,所以每次 UI 需要被渲染时该方都会被调用,但是为了避免影响渲染效率,请不要在这里做创建 Widget 的其它操作!
didUpdateWidget
当父 Widget 发生改变并需要进行重绘时,该方法就会被调用,该方法还接收一个 oldWidget 参数,可以使用它与当前 Widget 进行比较来做一些额外的逻辑处理。
@override
void didUpdateWidget(covariant LXFStatefulPage oldWidget) {
super.didUpdateWidget(oldWidget);
print("lxf -- didUpdateWidget");
}
deactivate
当 State 对象从树中被移除时会调用该方法(包括从树中移除又被插入到树中其它位置的情况)
dispose
当 State 对象从树中被移除并且不再被构建时会调用该方法,常用于取消对 streams 的订阅等释放资源的操作