《Flutter For Android學(xué)習(xí)日記》Widget簡(jiǎn)介

概念

在前面的介紹中隔披,我們知道在Flutter中幾乎所有的對(duì)象都是一個(gè)Widget收津。與原生開發(fā)中“控件”不同的是裳扯,F(xiàn)lutter中的Widget的概念更廣泛澄耍,它不僅可以表示UI元素噪珊,也可以表示一些功能性的組件如:用于手勢(shì)檢測(cè)GestureDetectorwidget晌缘、用于APP主題數(shù)據(jù)傳遞的Theme等等,而原生開發(fā)中的控件通常只是指UI元素痢站。在后面的內(nèi)容中磷箕,我們?cè)诿枋鯱I元素時(shí)可能會(huì)用到“控件”、“組件”這樣的概念阵难,讀者心里需要知道他們就是widget岳枷,只是在不同場(chǎng)景的不同表述而已。由于Flutter主要就是用于構(gòu)建用戶界面的呜叫,所以空繁,在大多數(shù)時(shí)候,讀者可以認(rèn)為widget就是一個(gè)控件朱庆,不必糾結(jié)于概念盛泡。

Widget與Element

在Flutter中,Widget的功能是“描述一個(gè)UI元素的配置數(shù)據(jù)”椎工,它就是說饭于,Widget其實(shí)并不是表示最終繪制在設(shè)備屏幕上的顯示元素蜀踏,而它只是描述顯示元素的一個(gè)配置數(shù)據(jù)维蒙。

實(shí)際上,F(xiàn)lutter中真正代表屏幕上顯示元素的類是Element果覆,也就是說Widget只是描述Element的配置數(shù)據(jù)颅痊!Widget只是UI元素的一個(gè)配置數(shù)據(jù),并且一個(gè)Widget可以對(duì)應(yīng)多個(gè)Element局待。這是因?yàn)橥粋€(gè)Widget對(duì)象可以被添加到UI樹的不同部分斑响,而真正渲染時(shí),UI樹的每一個(gè)Element節(jié)點(diǎn)都會(huì)對(duì)應(yīng)一個(gè)Widget對(duì)象钳榨〗⒎#總結(jié)一下:

Widget實(shí)際上就是Element的配置數(shù)據(jù),Widget樹實(shí)際上是一個(gè)配置樹薛耻,而真正的UI渲染樹是由Element構(gòu)成营罢;不過,由于Element是通過Widget生成的饼齿,所以它們之間有對(duì)應(yīng)關(guān)系饲漾,在大多數(shù)場(chǎng)景,我們可以寬泛地認(rèn)為Widget樹就是指UI控件樹或UI渲染樹缕溉。
一個(gè)Widget對(duì)象可以對(duì)應(yīng)多個(gè)Element對(duì)象考传。這很好理解,根據(jù)同一份配置(Widget)证鸥,可以創(chuàng)建多個(gè)實(shí)例(Element)僚楞。
讀者應(yīng)該將這兩點(diǎn)牢記在心中勤晚。

Widget主要接口

@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;

  @protected
  Element createElement();

  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

Widget類繼承自DiagnosticableTreeDiagnosticableTree“診斷樹”泉褐,主要作用是提供調(diào)試信息运翼。
Key: 這個(gè)key屬性類似于React/Vue中的key,主要的作用是決定是否在下一次build時(shí)復(fù)用舊的widget兴枯,決定的條件在canUpdate()方法中血淌。
createElement():正如前文所述“一個(gè)Widget可以對(duì)應(yīng)多個(gè)Element”;Flutter Framework在構(gòu)建UI樹時(shí)财剖,會(huì)先調(diào)用此方法生成對(duì)應(yīng)節(jié)點(diǎn)的Element對(duì)象悠夯。此方法是Flutter Framework隱式調(diào)用的,在我們開發(fā)過程中基本不會(huì)調(diào)用到躺坟。
debugFillProperties(...) 復(fù)寫父類的方法沦补,主要是設(shè)置診斷樹的一些特性。
canUpdate(...)是一個(gè)靜態(tài)方法咪橙,它主要用于在Widget樹重新build時(shí)復(fù)用舊的widget夕膀,其實(shí)具體來說,應(yīng)該是:是否用新的Widget對(duì)象去更新舊UI樹上所對(duì)應(yīng)的Element對(duì)象的配置美侦;通過其源碼我們可以看到产舞,只要newWidgetoldWidgetruntimeTypekey同時(shí)相等時(shí)就會(huì)用newWidget去更新Element對(duì)象的配置,否則就會(huì)創(chuàng)建新的Element菠剩。
另外Widget類本身是一個(gè)抽象類易猫,其中最核心的就是定義了createElement()接口,在Flutter開發(fā)中具壮,我們一般都不用直接繼承Widget類來實(shí)現(xiàn)一個(gè)新組件准颓,相反,我們通常會(huì)通過繼承StatelessWidgetStatefulWidget來間接繼承Widget類來實(shí)現(xiàn)棺妓。StatelessWidgetStatefulWidget都是直接繼承自Widget類攘已,而這兩個(gè)類也正是Flutter中非常重要的兩個(gè)抽象類,它們引入了兩種Widget模型怜跑,接下來我們將重點(diǎn)介紹一下這兩個(gè)類样勃。

StatelessWidget

我們已經(jīng)簡(jiǎn)單介紹過StatelessWidgetStatelessWidget相對(duì)比較簡(jiǎn)單妆艘,它繼承自Widget類彤灶,重寫了createElement()方法:

@override
StatelessElement createElement() => new StatelessElement(this);

StatelessElement間接繼承自Element類,與StatelessWidget相對(duì)應(yīng)(作為其配置數(shù)據(jù))批旺。
StatelessWidget用于不需要維護(hù)狀態(tài)的場(chǎng)景幌陕,它通常在build方法中通過嵌套其它Widget來構(gòu)建UI,在構(gòu)建過程中會(huì)遞歸的構(gòu)建其嵌套的Widget汽煮。我們看一個(gè)簡(jiǎn)單的例子:

class Echo extends StatelessWidget {
  const Echo({
    Key key,  
    @required this.text,
    this.backgroundColor:Colors.grey,
  }):super(key:key);

  final String text;
  final Color backgroundColor;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: backgroundColor,
        child: Text(text),
      ),
    );
  }
}

然后我們可以通過如下方式使用它:

Widget build(BuildContext context) {
  return Echo(text: "hello world");
}
運(yùn)行結(jié)果

StatefulWidget

StatelessWidget一樣搏熄,StatefulWidget也是繼承自Widget類棚唆,并重寫了createElement()方法,不同的是返回的Element對(duì)象并不相同心例;另外StatefulWidget類中添加了一個(gè)新的接口createState()宵凌。

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);

  @override
  StatefulElement createElement() => new StatefulElement(this);

  @protected
  State createState();
}

StatefulElement間接繼承自Element類,與StatefulWidget相對(duì)應(yīng)(作為其配置數(shù)據(jù))止后。StatefulElement中可能會(huì)多次調(diào)用createState()來創(chuàng)建狀態(tài)(State)對(duì)象瞎惫。
createState()用于創(chuàng)建和Stateful widget相關(guān)的狀態(tài),它在Stateful widget的生命周期中可能會(huì)被多次調(diào)用译株。例如瓜喇,當(dāng)一個(gè)Stateful widget同時(shí)插入到widget樹的多個(gè)位置時(shí),F(xiàn)lutter framework就會(huì)調(diào)用該方法為每一個(gè)位置生成一個(gè)獨(dú)立的State實(shí)例歉糜,其實(shí)乘寒,本質(zhì)上就是一個(gè)StatefulElement對(duì)應(yīng)一個(gè)State實(shí)例。

State

一個(gè)StatefulWidget類會(huì)對(duì)應(yīng)一個(gè)State類匪补,State表示與其對(duì)應(yīng)的StatefulWidget要維護(hù)的狀態(tài)伞辛,State中的保存的狀態(tài)信息可以:

  • widget構(gòu)建時(shí)可以被同步讀取。
  • widget生命周期中可以被改變夯缺,當(dāng)State被改變時(shí)蚤氏,可以手動(dòng)調(diào)用其setState()方法通知Flutter framework狀態(tài)發(fā)生改變喳逛,F(xiàn)lutter framework在收到消息后,會(huì)重新調(diào)用其build方法重新構(gòu)建widget樹润文,從而達(dá)到更新UI的目的殿怜。

State中有兩個(gè)常用屬性:
1.widget,它表示與該State實(shí)例關(guān)聯(lián)的widget實(shí)例头谜,由Flutter framework動(dòng)態(tài)設(shè)置。注意柱告,這種關(guān)聯(lián)并非永久的,因?yàn)樵趹?yīng)用生命周期中际度,UI樹上的某一個(gè)節(jié)點(diǎn)的widget實(shí)例在重新構(gòu)建時(shí)可能會(huì)變化,但State實(shí)例只會(huì)在第一次插入到樹中時(shí)被創(chuàng)建乖菱,當(dāng)在重新構(gòu)建時(shí)蓬网,如果widget被修改了鹉勒,F(xiàn)lutter framework會(huì)動(dòng)態(tài)設(shè)置State.widget為新的widget實(shí)例。

2.context禽额。StatefulWidget對(duì)應(yīng)的BuildContext,作用同StatelessWidgetBuildContext脯倒。

State生命周期

理解State的生命周期對(duì)flutter開發(fā)非常重要哲鸳,為了加深讀者印象,本節(jié)我們通過一個(gè)實(shí)例來演示一下State生命周期盔憨。在接下來的示例中徙菠,我們實(shí)現(xiàn)一個(gè)計(jì)數(shù)器widget,點(diǎn)擊它可以使計(jì)數(shù)器加1郁岩,由于要保存計(jì)數(shù)器的數(shù)值狀態(tài)婿奔,所以我們應(yīng)繼承StatefulWidget,代碼如下:

class CounterWidget extends StatefulWidget {
  const CounterWidget({
    Key key,
    this.initValue: 0
  });

  final int initValue;

  @override
  _CounterWidgetState createState() => new _CounterWidgetState();
}

CounterWidget接收一個(gè)initValue整型參數(shù)问慎,它表示計(jì)數(shù)器的初始值萍摊。下面我們看一下State的代碼:

class _CounterWidgetState extends State<CounterWidget> {  
  int _counter;

  @override
  void initState() {
    super.initState();
    //初始化狀態(tài)  
    _counter=widget.initValue;
    print("initState");
  }

  @override
  Widget build(BuildContext context) {
    print("build");
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text('$_counter'),
          //點(diǎn)擊后計(jì)數(shù)器自增
          onPressed:()=>setState(()=> ++_counter,
          ),
        ),
      ),
    );
  }

  @override
  void didUpdateWidget(CounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }

  @override
  void deactivate() {
    super.deactivate();
    print("deactive");
  }

  @override
  void dispose() {
    super.dispose();
    print("dispose");
  }

  @override
  void reassemble() {
    super.reassemble();
    print("reassemble");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }

}

接下來,我們創(chuàng)建一個(gè)新路由如叼,在新路由中冰木,我們只顯示一個(gè)CounterWidget

Widget build(BuildContext context) {
  return CounterWidget();
}

我們運(yùn)行應(yīng)用并打開該路由頁面,在新路由頁打開后笼恰,屏幕中央就會(huì)出現(xiàn)一個(gè)數(shù)字0踊沸,然后控制臺(tái)日志輸出:

I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build

可以看到,在StatefulWidget插入到Widget樹時(shí)首先initState方法會(huì)被調(diào)用社证。
然后我們點(diǎn)擊??按鈕熱重載逼龟,控制臺(tái)輸出日志如下:

I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget
I/flutter ( 5436): build

可以看到此時(shí)initStatedidChangeDependencies都沒有被調(diào)用,而此時(shí)didUpdateWidget被調(diào)用追葡。

接下來腺律,我們?cè)?code>widget樹中移除CounterWidget,將路由build方法改為:

Widget build(BuildContext context) {
  //移除計(jì)數(shù)器 
  //return CounterWidget();
  //隨便返回一個(gè)Text()
  return Text("xxx");
}

然后熱重載宜肉,日志如下:

I/flutter ( 5436): reassemble
I/flutter ( 5436): deactive
I/flutter ( 5436): dispose

我們可以看到匀钧,在CounterWidgetwidget樹中移除時(shí),deactivedispose會(huì)依次被調(diào)用谬返。
下面我們來看看各個(gè)回調(diào)函數(shù):

  • initState:當(dāng)Widget第一次插入到Widget樹時(shí)會(huì)被調(diào)用之斯,對(duì)于每一個(gè)State對(duì)象,F(xiàn)lutter framework只會(huì)調(diào)用一次該回調(diào)朱浴,所以吊圾,通常在該回調(diào)中做一些一次性的操作达椰,如狀態(tài)初始化、訂閱子樹的事件通知等项乒。不能在該回調(diào)中調(diào)用BuildContext.dependOnInheritedWidgetOfExactType(該方法用于在Widget樹上獲取離當(dāng)前widget最近的一個(gè)父級(jí)InheritFromWidget啰劲,關(guān)于InheritedWidget我們將在后面章節(jié)介紹),原因是在初始化完成后檀何,Widget樹中的InheritFromWidget也可能會(huì)發(fā)生變化蝇裤,所以正確的做法應(yīng)該在在build()方法或didChangeDependencies()中調(diào)用它栓辜。

  • didChangeDependencies():當(dāng)State對(duì)象的依賴發(fā)生變化時(shí)會(huì)被調(diào)用;例如:在之前build() 中包含了一個(gè)InheritedWidget藕甩,然后在之后的build()InheritedWidget發(fā)生了變化狭莱,那么此時(shí)InheritedWidget的子widgetdidChangeDependencies()回調(diào)都會(huì)被調(diào)用概作。典型的場(chǎng)景是當(dāng)系統(tǒng)語言Locale或應(yīng)用主題改變時(shí)讯榕,F(xiàn)lutter framework會(huì)通知widget調(diào)用此回調(diào)。

  • build():此回調(diào)讀者現(xiàn)在應(yīng)該已經(jīng)相當(dāng)熟悉了济竹,它主要是用于構(gòu)建Widget子樹的规辱,會(huì)在如下場(chǎng)景被調(diào)用:

    1.在調(diào)用initState()之后栽燕。
    2.在調(diào)用didUpdateWidget()之后碍岔。
    3.在調(diào)用setState()之后蔼啦。
    4.在調(diào)用didChangeDependencies()之后捏肢。
    5.在State對(duì)象從樹中一個(gè)位置移除后(會(huì)調(diào)用deactivate)又重新插入到樹的其它位置之后。

  • reassemble():此回調(diào)是專門為了開發(fā)調(diào)試而提供的衣屏,在熱重載(hot reload)時(shí)會(huì)被調(diào)用辩棒,此回調(diào)在Release模式下永遠(yuǎn)不會(huì)被調(diào)用一睁。

  • didUpdateWidget():在widget重新構(gòu)建時(shí),F(xiàn)lutter framework會(huì)調(diào)用Widget.canUpdate來檢測(cè)Widget樹中同一位置的新舊節(jié)點(diǎn)窘俺,然后決定是否需要更新瘤泪,如果Widget.canUpdate返回true則會(huì)調(diào)用此回調(diào)染坯。正如之前所述单鹿,Widget.canUpdate會(huì)在新舊widgetkeyruntimeType同時(shí)相等時(shí)會(huì)返回true,也就是說在在新舊widgetkeyruntimeType同時(shí)相等時(shí)didUpdateWidget()就會(huì)被調(diào)用劲妙。

  • deactivate():當(dāng)State對(duì)象從樹中被移除時(shí)镣奋,會(huì)調(diào)用此回調(diào)侨颈。在一些場(chǎng)景下芯义,F(xiàn)lutter framework會(huì)將State對(duì)象重新插到樹中扛拨,如包含此State對(duì)象的子樹在樹的一個(gè)位置移動(dòng)到另一個(gè)位置時(shí)(可以通過GlobalKey來實(shí)現(xiàn))。如果移除后沒有重新插入到樹中則緊接著會(huì)調(diào)用dispose()方法求泰。

  • dispose():當(dāng)State對(duì)象從樹中被永久移除時(shí)調(diào)用渴频;通常在此回調(diào)中釋放資源。
    StatefulWidget生命周期如圖3-2所示:

    StatefulWidget生命周期

注意:在繼承StatefulWidget重寫其方法時(shí)志衍,對(duì)于包含@mustCallSuper標(biāo)注的父類方法楼肪,都要在子類方法中先調(diào)用父類方法春叫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末暂殖,一起剝皮案震驚了整個(gè)濱河市呛每,隨后出現(xiàn)的幾起案子坡氯,更是在濱河造成了極大的恐慌,老刑警劉巖手形,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件库糠,死亡現(xiàn)場(chǎng)離奇詭異瞬欧,居然都是意外死亡黍判,警方通過查閱死者的電腦和手機(jī)篙梢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門渤滞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妄呕,“玉大人,你說我怎么就攤上這事肿孵∈栉海” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵蛉腌,是天一觀的道長(zhǎng)烙丛。 經(jīng)常有香客問我羔味,道長(zhǎng),這世上最難降的妖魔是什么忘蟹? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任寒瓦,我火速辦了婚禮坪仇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喂很。我一直安慰自己少辣,他們只是感情好羡蛾,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著器予,像睡著了一般捐迫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上反浓,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天雷则,我揣著相機(jī)與錄音巧婶,去河邊找鬼涂乌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛湾盒,可吹牛的內(nèi)容都是我干的徽诲。 我是一名探鬼主播尖殃,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼送丰,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼器躏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起遏佣,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤状婶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后太抓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體走敌,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掉丽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年捶障,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纲刀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锭部,死狀恐怖面褐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情湃窍,我是刑警寧澤您市,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布茵休,位于F島的核電站映挂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏帽撑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一扣蜻、第九天 我趴在偏房一處隱蔽的房頂上張望及塘。 院中可真熱鬧笙僚,春花似錦、人聲如沸肋层。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雌团。三九已至,卻和暖如春辱姨,著一層夾襖步出監(jiān)牢的瞬間雨涛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工凉泄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留后众,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓颅拦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親右锨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子碌秸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345