Flutter學(xué)習(xí)筆記5-StatelessWidget、StatefulWidget

本篇主要對(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è)商品列表,效果如下:


Simulator Screen Shot - iPhone 12 - 2021-08-22 at 10.20.36.png
  1. StatelessWidget參數(shù)傳遞
    與Dart語(yǔ)法一致,創(chuàng)建屬性(final修飾),實(shí)現(xiàn)初始化方法,進(jìn)行賦值
    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)]);
      }
    }
    
    調(diào)用:
    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")
    
  2. 豎向列表
  • column: 豎向布局多個(gè)控件,使用column布局,當(dāng)豎向控件高度超出屏幕高度后,會(huì)顯示條紋遮擋
  • ListView: 豎向可滾動(dòng)控件
  1. 對(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ì)存在

  2. 創(chuàng)建控件之間的邊距
    可以通過(guò)SizedBox控件來(lái)實(shí)現(xiàn),橫向距離width,豎向距離height

    SizedBox(height: 8)
    
  3. 邊框創(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)型
    
  4. 控制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

  1. 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
  1. 使用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)行渲染

  1. 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生命周期

  1. 生命周期的作用(意義)
  • 初始化一些數(shù)據(jù)、狀態(tài)、變量
  • 發(fā)送網(wǎng)絡(luò)請(qǐng)求時(shí)機(jī)
  • 進(jìn)行一些事件的監(jiān)聽(tīng)
  • 管理內(nèi)存(手動(dòng)銷(xiāo)毀)
  1. 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();
  }
}
  1. 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)行
  1. 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 的訂閱等釋放資源的操作

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容