本篇主要對StatelessWidget、StatefulWidget做一個簡單的介紹井辜,并實際運用來進行一些簡單界面的構(gòu)建
一、StatelessWidget
StatelessWidget子類代碼塊生成快捷鍵: stless
上篇已經(jīng)簡單介紹了StatelessWidget的基本使用管闷,接下來簡單介紹如何使用StatelessWidget創(chuàng)建一個商品列表粥脚,效果如下:
- StatelessWidget參數(shù)傳遞
與Dart語法一致,創(chuàng)建屬性(final修飾)包个,實現(xià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: 豎向布局多個控件碧囊,使用column布局树灶,當豎向控件高度超出屏幕高度后,會顯示條紋遮擋
- ListView: 豎向可滾動控件
對象創(chuàng)建:
函數(shù)內(nèi)部可以創(chuàng)建局部變量呕臂,減小語句內(nèi)部代碼量破托,但是每次調(diào)用函數(shù)都會創(chuàng)建
在類的內(nèi)部創(chuàng)建變量,則只會在對象實例化時創(chuàng)建一次
在類的外部創(chuàng)建變量歧蒋,則在整個項目生命周期中都會存在-
創(chuàng)建控件之間的邊距
可以通過SizedBox控件來實現(xiàn)土砂,橫向距離width,豎向距離heightSizedBox(height: 8)
-
邊框創(chuàng)建
將某個控件放置到Container中谜洽,可以用Container來設(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) ]));
問題:BoxDecoration的主要用處萝映?
用于Widget的裝飾color 顏色背景 Color類型 image 圖片背景 DecorationImage類型 border 邊界 BoxBorder類型 borderRadius 圓角邊界半徑 BorderRadiusGeometry類型 boxShadow 陰影 List<BoxShadow>類型 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<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("購物車")), 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("花生", "鹽水花生阐虚,補氣養(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("八寶粥", "居家旅行序臂,出門良品",
"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)需要改變的時候要使用StatefulWidget
- StatefulWidget內(nèi)部有一個抽象方法createState实束,此方法返回一個State類奥秆,繼承自StatefulWidget的類需要實現(xiàn)此方法
- 在自定義的State子類中需要實現(xiàn)build方法生成一個控件
- StatefulWidget內(nèi)部不可直接定義狀態(tài),但是可以在自定義的State子類中可以定義狀態(tài)
問題:為什么Flutter在設(shè)計的時候咸灿,StatefulWidget的build方法放在State中构订?
- build出來的widget是需要依賴State中的變量(狀態(tài)/數(shù)據(jù))
- 在Flutter的運行過程中,widget是不斷銷毀和創(chuàng)建的避矢,當我們的狀態(tài)發(fā)生改變時悼瘾,并不希望重新創(chuàng)建一個新的State
-
使用StatefulWidget構(gòu)建一個計數(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("購物車")), 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("當前計數(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--;
}
});
})
]);
}
}
問題:數(shù)值改變能放在setState外面嗎审胸?
setState會觸發(fā)widget build方法重新進行渲染
- StatefulWidget傳值
父類給子類傳值亥宿,傳值方式與StatelessWdight語法一致,創(chuàng)建屬性(final修飾)砂沛,實現(xiàn)對應(yīng)初始化方法烫扼,進行賦值,將參數(shù)傳遞給Widget子類
然后在State類中通過this.widget來實現(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類定義的泛型需要跟之前創(chuàng)建的StatefulWidget子類保持一致的原因
三碍庵、StatelessWidget材蛛、StatefulWidget生命周期
- 生命周期的作用(意義)
- 初始化一些數(shù)據(jù)圆到、狀態(tài)、變量
- 發(fā)送網(wǎng)絡(luò)請求時機
- 進行一些事件的監(jiān)聽
- 管理內(nèi)存(手動銷毀)
- StatelessWidget生命周期
這里比較簡單卑吭,只有兩個生命周期
- 構(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子類的構(gòu)造方法
- 第二步:調(diào)用StatefulWidget子類的createState方法
- 第三步:調(diào)用State子類的構(gòu)造方法
- 第四步 調(diào)用State子類的initState方法
用于狀態(tài)的初始化芽淡,會在State類完成構(gòu)造之后,build方法執(zhí)行之前豆赏,進行執(zhí)行
initState方法使用@mustCallSuper標記挣菲,必須調(diào)用父類initState方法 - 第五步:執(zhí)行State子類的build方法
- 最后,當前的Widget子類不再使用時掷邦,會執(zhí)行State子類的dispose方法白胀,進行內(nèi)存的釋放
此外:
- didChangeDependencies方法 在initState方法執(zhí)行后,或者從其他對象中依賴一些數(shù)據(jù)發(fā)生改變時抚岗,比如inheritedWidget或杠,會執(zhí)行此方法
- didUpdateWidget方法 當重新創(chuàng)建Widget子類(rebuild),會觸發(fā)state子類的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方法會在內(nèi)部觸發(fā)重新運行build方法宣蔚,根據(jù)最新的狀態(tài)返回新的控件向抢,所以狀態(tài)的修改需要在setState方法中進行
- StatefulWidget生命周期的復(fù)雜版
在StatefulWidget普通生命周期的基礎(chǔ)上,還有一些復(fù)雜的過程未提及到
- mounted
- dirty state & clean state
疑問:意義及用法胚委?
5.生命周期擴展
createState
當構(gòu)建一個 StatefulWidget 會立即調(diào)用該方法
@override
_MyStatefulPageState createState() {
print("lxf -- createState");
return _MyStatefulPageState();
}
initState
在 Widget 被創(chuàng)建出來并插入到樹中時被調(diào)用的方法挟鸠,只會被調(diào)用一次,等價于安卓的 onCreate() 或 iOS 中的 viewDidLoad()亩冬,所以在此時視圖還未被渲染艘希,但是 Widget 已經(jīng)被插入到樹中,一般用于執(zhí)行一些初始化操作
@override
void initState() {
super.initState();
print("lxf -- initState");
// 初始化操作
...
}
mounted
所有的 Widget 都擁有這個屬性硅急,當 buildContext 被分配且當前在樹中時覆享,該值為 true,直到調(diào)用 dispose 時重置為 false
addPostFrameCallback
單次 Frame(幀) 繪制回調(diào)营袜,在當前幀繪制完成后進行回調(diào)淹真,用于在 Widget 渲染完畢之后做一些操作,該回調(diào)只會進行一次连茧,如果需要再次監(jiān)聽則需要再次設(shè)置。
@override
void initState() {
super.initState();
print("lxf -- initState");
// 單次幀繪制回調(diào)(只會回調(diào)一次巍糯,如果要再次監(jiān)聽需要再設(shè)置)
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
print("lxf -- addPostFrameCallback");
});
}
如果希望在實時繪制幀時進行回調(diào)啸驯,則使用 addPersistentFrameCallback
// 實時幀繪制回調(diào)(每次繪制幀結(jié)束后都會回調(diào))
WidgetsBinding.instance?.addPersistentFrameCallback((timeStamp) {
print("lxf -- addPersistentFrameCallback");
});
didChangeDependencies
在 initState() 方法被執(zhí)行后立刻被調(diào)用,之后當依賴的 InheritedWidget 發(fā)生變化時祟峦,框架將會再次調(diào)用該方法將變化通知給當前對象罚斗。
build
在 didChangeDependencies 之后被調(diào)用,在該方法中會將 Widget 進行渲染宅楞,且由于再次渲染的操作是廉價的针姿,所以每次 UI 需要被渲染時該方都會被調(diào)用袱吆,但是為了避免影響渲染效率,請不要在這里做創(chuàng)建 Widget 的其它操作距淫!
didUpdateWidget
當父 Widget 發(fā)生改變并需要進行重繪時绞绒,該方法就會被調(diào)用,該方法還接收一個 oldWidget 參數(shù)榕暇,可以使用它與當前 Widget 進行比較來做一些額外的邏輯處理蓬衡。
@override
void didUpdateWidget(covariant LXFStatefulPage oldWidget) {
super.didUpdateWidget(oldWidget);
print("lxf -- didUpdateWidget");
}
deactivate
當 State 對象從樹中被移除時會調(diào)用該方法(包括從樹中移除又被插入到樹中其它位置的情況)
dispose
當 State 對象從樹中被移除并且不再被構(gòu)建時會調(diào)用該方法,常用于取消對 streams 的訂閱等釋放資源的操作