本篇主要對(duì)StatelessWidget、StatefulWidget做一個(gè)簡(jiǎn)單的介紹,并實(shí)際運(yùn)用來(lái)進(jìn)行一些簡(jiǎn)單界面的構(gòu)建
一、StatelessWidget
StatelessWidget子類(lèi)代碼塊生成快捷鍵: stless
上篇已經(jīng)簡(jiǎn)單介紹了StatelessWidget的基本使用,接下來(lái)簡(jiǎn)單介紹如何使用StatelessWidget創(chuàng)建一個(gè)商品列表,效果如下:

- StatelessWidget參數(shù)傳遞
與Dart語(yǔ)法一致,創(chuàng)建屬性(final修飾),實(shí)現(xiàn)初始化方法,進(jìn)行賦值
調(diào)用: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: 豎向布局多個(gè)控件,使用column布局,當(dāng)豎向控件高度超出屏幕高度后,會(huì)顯示條紋遮擋
- ListView: 豎向可滾動(dòng)控件
對(duì)象創(chuàng)建:
函數(shù)內(nèi)部可以創(chuàng)建局部變量,減小語(yǔ)句內(nèi)部代碼量,但是每次調(diào)用函數(shù)都會(huì)創(chuàng)建
在類(lèi)的內(nèi)部創(chuàng)建變量,則只會(huì)在對(duì)象實(shí)例化時(shí)創(chuàng)建一次
在類(lèi)的外部創(chuàng)建變量,則在整個(gè)項(xiàng)目生命周期中都會(huì)存在-
創(chuàng)建控件之間的邊距
可以通過(guò)SizedBox控件來(lái)實(shí)現(xiàn),橫向距離width,豎向距離heightSizedBox(height: 8) -
邊框創(chuàng)建
將某個(gè)控件放置到Container中,可以用Container來(lái)設(shè)置邊框(decoration)以及內(nèi)邊距(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) ]));問(wèn)題:BoxDecoration的主要用處?
用于Widget的裝飾color 顏色背景 Color類(lèi)型 image 圖片背景 DecorationImage類(lèi)型 border 邊界 BoxBorder類(lèi)型 borderRadius 圓角邊界半徑 BorderRadiusGeometry類(lèi)型 boxShadow 陰影 List<BoxShadow>類(lèi)型 gradient 漸變色 Gradient類(lèi)型 backgroundBlendMode 背景混合模式 BlendMode類(lèi)型 shape 形狀 BoxShape類(lèi)型 -
控制column豎向布局控件位置
控件分為主軸(豎向)和交叉軸(橫向)
主軸使用MainAxisAlignment控制子控件位置
交叉軸使用CrossAxisAlignment控制子控件位置
通過(guò)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<String> 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("購(gòu)物車(chē)")), 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("花生", "鹽水花生,補(bǔ)氣養(yǎng)血",
"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("八寶粥", "居家旅行,出門(mén)良品",
"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用法
有狀態(tài)需要改變的時(shí)候要使用StatefulWidget
- StatefulWidget內(nèi)部有一個(gè)抽象方法createState,此方法返回一個(gè)State類(lèi),繼承自StatefulWidget的類(lèi)需要實(shí)現(xiàn)此方法
- 在自定義的State子類(lèi)中需要實(shí)現(xiàn)build方法生成一個(gè)控件
- StatefulWidget內(nèi)部不可直接定義狀態(tài),但是可以在自定義的State子類(lèi)中可以定義狀態(tài)
問(wèn)題:為什么Flutter在設(shè)計(jì)的時(shí)候,StatefulWidget的build方法放在State中?
- build出來(lái)的widget是需要依賴(lài)State中的變量(狀態(tài)/數(shù)據(jù))
- 在Flutter的運(yùn)行過(guò)程中,widget是不斷銷(xiāo)毀和創(chuàng)建的,當(dāng)我們的狀態(tài)發(fā)生改變時(shí),并不希望重新創(chuàng)建一個(gè)新的State
-
使用StatefulWidget構(gòu)建一個(gè)計(jì)數(shù)器
效果圖如下:
Simulator Screen Shot - iPhone 12 - 2021-08-21 at 20.08.41.png
其中按鈕可以使用ElevatedButton(RaisedButton已廢棄)
使用Column構(gòu)建豎向組件,Row構(gòu)建橫向組件
import 'package:flutter/material.dart';
main(List<String> 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("購(gòu)物車(chē)")), body: YWHomeContent());
}
}
class YWHomeContent extends StatefulWidget {
@override
_YWHomeContentState createState() => _YWHomeContentState();
}
class _YWHomeContentState extends State<YWHomeContent> {
int number = 0;
@override
Widget build(BuildContext context) {
return Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
getButtons(),
Text("當(dāng)前計(jì)數(shù):$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--;
}
});
})
]);
}
}
問(wèn)題:數(shù)值改變能放在setState外面嗎?
setState會(huì)觸發(fā)widget build方法重新進(jìn)行渲染
- StatefulWidget傳值
父類(lèi)給子類(lèi)傳值,傳值方式與StatelessWdight語(yǔ)法一致,創(chuàng)建屬性(final修飾),實(shí)現(xiàn)對(duì)應(yīng)初始化方法,進(jìn)行賦值,將參數(shù)傳遞給Widget子類(lèi)
然后在State類(lèi)中通過(guò)this.widget來(lái)實(shí)現(xiàn)取值class YWHomeContent extends StatefulWidget { final String message; YWHomeContent(this.message); @override _YWHomeContentState createState() =>_YWHomeContentState(); } class _YWHomeContentState extends State<YWHomeContent> { int number = 0; @override Widget build(BuildContext context) { return Text("${this.widget.message}"); } }
這也就是為什么State類(lèi)定義的泛型需要跟之前創(chuàng)建的StatefulWidget子類(lèi)保持一致的原因
三、StatelessWidget、StatefulWidget生命周期
- 生命周期的作用(意義)
- 初始化一些數(shù)據(jù)、狀態(tài)、變量
- 發(fā)送網(wǎng)絡(luò)請(qǐng)求時(shí)機(jī)
- 進(jìn)行一些事件的監(jiān)聽(tīng)
- 管理內(nèi)存(手動(dòng)銷(xiāo)毀)
- StatelessWidget生命周期
這里比較簡(jiǎn)單,只有兩個(gè)生命周期
- 構(gòu)造函數(shù)被調(diào)用
- 調(diào)用build方法
class TestWidget extends StatelessWidget {
TestWidget() {
print("構(gòu)造函數(shù)被創(chuàng)建");
}
@override
Widget build(BuildContext context) {
print("build方法被調(diào)用");
return Container();
}
}
-
StatefulWidget生命周期
image.png
- 第一步:調(diào)用StatefulWidget子類(lèi)的構(gòu)造方法
- 第二步:調(diào)用StatefulWidget子類(lèi)的createState方法
- 第三步:調(diào)用State子類(lèi)的構(gòu)造方法
- 第四步 調(diào)用State子類(lèi)的initState方法
用于狀態(tài)的初始化,會(huì)在State類(lèi)完成構(gòu)造之后,build方法執(zhí)行之前,進(jìn)行執(zhí)行
initState方法使用@mustCallSuper標(biāo)記,必須調(diào)用父類(lèi)initState方法 - 第五步:執(zhí)行State子類(lèi)的build方法
- 最后,當(dāng)前的Widget子類(lèi)不再使用時(shí),會(huì)執(zhí)行State子類(lèi)的dispose方法,進(jìn)行內(nèi)存的釋放
此外:
- didChangeDependencies方法 在initState方法執(zhí)行后,或者從其他對(duì)象中依賴(lài)一些數(shù)據(jù)發(fā)生改變時(shí),比如inheritedWidget,會(huì)執(zhí)行此方法
- didUpdateWidget方法 當(dāng)重新創(chuàng)建Widget子類(lèi)(rebuild),會(huì)觸發(fā)state子類(lèi)的didUpdateWidget方法
代碼如下所示:
class YWHomeContent extends StatefulWidget {
YWHomeContent() {
print("1.調(diào)用YWHomeContent的初始化方法");
}
@override
_YWHomeContentState createState() {
print("2.調(diào)用YWHomeContent的createState方法");
return _YWHomeContentState();
}
}
class _YWHomeContentState extends State<YWHomeContent> {
_YWHomeContentState() {
print("3.調(diào)用_YWHomeContentState的初始化方法");
}
@override
void initState() {
super.initState();
print("4.調(diào)用_YWHomeContentState的initState方法");
}
@override
void didChangeDependencies() {
print("調(diào)用_YWHomeContentState的didChangeDependencies方法");
super.didChangeDependencies();
}
@override
void didUpdateWidget(covariant YWHomeContent oldWidget) {
print("調(diào)用_YWHomeContentState的didUpdateWidget方法");
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
print("5.調(diào)用_YWHomeContentState的build方法");
return Text("Hello World");
}
@override
void dispose() {
super.dispose();
print("6.調(diào)用_YWHomeContentState的dispose方法");
}
}
- setState方法會(huì)在內(nèi)部觸發(fā)重新運(yùn)行build方法,根據(jù)最新的狀態(tài)返回新的控件,所以狀態(tài)的修改需要在setState方法中進(jìn)行
- StatefulWidget生命周期的復(fù)雜版
在StatefulWidget普通生命周期的基礎(chǔ)上,還有一些復(fù)雜的過(guò)程未提及到
- mounted
- dirty state & clean state
疑問(wèn):意義及用法?
5.生命周期擴(kuò)展
createState
當(dāng)構(gòu)建一個(gè) StatefulWidget 會(huì)立即調(diào)用該方法
@override
_MyStatefulPageState createState() {
print("lxf -- createState");
return _MyStatefulPageState();
}
initState
在 Widget 被創(chuàng)建出來(lái)并插入到樹(shù)中時(shí)被調(diào)用的方法,只會(huì)被調(diào)用一次,等價(jià)于安卓的 onCreate() 或 iOS 中的 viewDidLoad(),所以在此時(shí)視圖還未被渲染,但是 Widget 已經(jīng)被插入到樹(shù)中,一般用于執(zhí)行一些初始化操作
@override
void initState() {
super.initState();
print("lxf -- initState");
// 初始化操作
...
}
mounted
所有的 Widget 都擁有這個(gè)屬性,當(dāng) buildContext 被分配且當(dāng)前在樹(shù)中時(shí),該值為 true,直到調(diào)用 dispose 時(shí)重置為 false
addPostFrameCallback
單次 Frame(幀) 繪制回調(diào),在當(dāng)前幀繪制完成后進(jìn)行回調(diào),用于在 Widget 渲染完畢之后做一些操作,該回調(diào)只會(huì)進(jìn)行一次,如果需要再次監(jiān)聽(tīng)則需要再次設(shè)置。
@override
void initState() {
super.initState();
print("lxf -- initState");
// 單次幀繪制回調(diào)(只會(huì)回調(diào)一次,如果要再次監(jiān)聽(tīng)需要再設(shè)置)
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
print("lxf -- addPostFrameCallback");
});
}
如果希望在實(shí)時(shí)繪制幀時(shí)進(jìn)行回調(diào),則使用 addPersistentFrameCallback
// 實(shí)時(shí)幀繪制回調(diào)(每次繪制幀結(jié)束后都會(huì)回調(diào))
WidgetsBinding.instance?.addPersistentFrameCallback((timeStamp) {
print("lxf -- addPersistentFrameCallback");
});
didChangeDependencies
在 initState() 方法被執(zhí)行后立刻被調(diào)用,之后當(dāng)依賴(lài)的 InheritedWidget 發(fā)生變化時(shí),框架將會(huì)再次調(diào)用該方法將變化通知給當(dāng)前對(duì)象。
build
在 didChangeDependencies 之后被調(diào)用,在該方法中會(huì)將 Widget 進(jìn)行渲染,且由于再次渲染的操作是廉價(jià)的,所以每次 UI 需要被渲染時(shí)該方都會(huì)被調(diào)用,但是為了避免影響渲染效率,請(qǐng)不要在這里做創(chuàng)建 Widget 的其它操作!
didUpdateWidget
當(dāng)父 Widget 發(fā)生改變并需要進(jìn)行重繪時(shí),該方法就會(huì)被調(diào)用,該方法還接收一個(gè) oldWidget 參數(shù),可以使用它與當(dāng)前 Widget 進(jìn)行比較來(lái)做一些額外的邏輯處理。
@override
void didUpdateWidget(covariant LXFStatefulPage oldWidget) {
super.didUpdateWidget(oldWidget);
print("lxf -- didUpdateWidget");
}
deactivate
當(dāng) State 對(duì)象從樹(shù)中被移除時(shí)會(huì)調(diào)用該方法(包括從樹(shù)中移除又被插入到樹(shù)中其它位置的情況)
dispose
當(dāng) State 對(duì)象從樹(shù)中被移除并且不再被構(gòu)建時(shí)會(huì)調(diào)用該方法,常用于取消對(duì) streams 的訂閱等釋放資源的操作

