Widget總結

Flutter中Widget爱只,State和BuildContext的概念是每個Flutter開發(fā)人員需要完全理解的最重要概念之一叫惊。這里先講解一下Widget以及Widget寞肖。三者之間的關系會在最后一篇總結一下。

Widget詳解

Widget類在Flutter中是非常重要的辩涝,繼承自Widget類的有PreferredSizeWidget贸伐、ProxyWidget、RenderObjectWidget怔揩、StatefulWidget捉邢、StatelessWidget。我們?nèi)粘J褂玫慕^大部分widget都是繼承自Widget類商膊,查看Widget類源碼伏伐,內(nèi)部實現(xiàn)非常簡單,構造函數(shù)如下:

const Widget({ this.key });
final Key key;

在flutter中構建APP是由widget樹構建起來的晕拆,所以這個key的作用是用來控制在widget樹中替換widget的時候使用的藐翎。其中Key類是Widget、Element以及SemanticsNode的唯一標識符实幕,繼承自Key的還有LocalKey以及GlobalKey吝镣。詳細可以去framework.dart文件查看相關源碼及說明。

Widget分類

在Flutter中昆庇,我們平時自定義的widget末贾,一般都是繼承自StatefulWidget或StatelessWidget(并不是只有這兩種),這兩種widget也是目前最常用的兩種凰锡。如果一個控件自身狀態(tài)不會去改變,創(chuàng)建了就直接顯示圈暗,不會有色值掂为、大小或者其他屬性的變化,這種widget一般都是繼承自StatelessWidget员串,常見的有Container勇哗、ScrollView等。如果一個控件需要動態(tài)的去改變或者相應一些狀態(tài)寸齐,例如點擊態(tài)欲诺、色值抄谐、內(nèi)容區(qū)域等,那么一般都是繼承自StatefulWidget扰法,常見的有CheckBox蛹含、AppBar、TabBar等塞颁。兩者的差別在于是否有狀態(tài)浦箱。


圖片1.png
  • StatelessWidget
    一個StatelessWidget可以用多個不同的BuildContext構建,它是由build函數(shù)構建的祠锣。BuildContext抽象類酷窥,它表示一個控件在整個控件樹中的位置句柄,每個控件都有自己的BuildContext實例伴网。某些靜態(tài)函數(shù)(例如showDialog蓬推、Theme.of等)也有BuildContext實例,以便它們可以代表調(diào)用控件或?qū)iT針對給定上下文獲取數(shù)據(jù)澡腾。BuildContext對象被傳遞給WidgetBuilder函數(shù)沸伏,它為創(chuàng)建控件的函數(shù)簽名,例如StatelessWidget.build或State.build蛋铆。無狀態(tài)窗口小部件只能在加載/構建窗口小部件時繪制一次馋评,這意味著無法基于任何事件或用戶操作重繪窗口小部件。


    圖片2.png

對于StatelessWidget刺啦,build方法會在如下三種情況下調(diào)用:

  1. widget第一次被插入到樹中留特;
  2. widget的父節(jié)點更改了配置(configuration);
  3. widget依賴的InheritedWidget(InheritedWidget是一個特殊的Widget玛瘸,您可以將其作為另一個子樹的父級放在Widgets樹中蜕青。該子樹的所有小部件都必須能夠與該InheritedWidget公開的數(shù)據(jù)進行交互,后面會詳細總結)改變了糊渊。
import 'package:flutter/material.dart';
import 'package:flutter_apptest/SplashPage.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {

  MyApp ({
        Key key,
        this.parameter,
    }): super(key:key);
  final parameter;

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SplashPage(),//啟動MainPage
    );
  }
}
  • StatefulWidget
    其他一些小部件將處理一些在Widget生命周期內(nèi)會發(fā)生變化的內(nèi)部數(shù)據(jù)右核。因此,該數(shù)據(jù)變得動態(tài)渺绒。
    此Widget保存的數(shù)據(jù)集可能會在此Widget的生命周期內(nèi)發(fā)生變化贺喝,稱為State。
    這些窗口小部件稱為有狀態(tài)窗口小部件(Stateful Widget)宗兼。
    這樣的Widget的示例可以是用戶可以選擇的復選框列表或者根據(jù)條件禁用的Button躏鱼。
    StatefulWidget創(chuàng)建需要一個State狀態(tài),通過createState函數(shù)返回殷绍。而一個StatefulWidget會為每個BuildContext創(chuàng)建一個State對象染苛。


    圖片3.png

    在了解這些狀態(tài)的時候,更多的是了解State狀態(tài)

import 'package:flutter/material.dart';

class MinePage extends StatefulWidget{

  //此部分在Widget的生命周期內(nèi)不會發(fā)生變化主到,但可能接受可由其相應的State實例使用的參數(shù)茶行。請注意躯概,在Widget的第一部分定義的任何變量通常在其生命周期內(nèi)不會更改。
  MyStatefulWidget({
        Key key,
        this.color,
    }): super(key: key);
    
    final Color color;

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new Page();
  }

}

class Page extends State<MinePage>{

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return layout(context);
  }

  Widget layout(BuildContext context) {
    // 改變狀態(tài)加以重新繪制
    setState(() {

    });
    return new Scaffold(
      appBar: buildAppBar(context),
      body: new Text('我的頁面'),
    );
  }

  buildAppBar(BuildContext context) {
    return new AppBar(title: const Text('我的'),);
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
  }
  
}

我們在創(chuàng)建State的時候可以看到和StatefulWidget相似的build方法畔师,也就是說我們也可以獲得一個BuildContext娶靡,在使用StatefulWidget.createState創(chuàng)建它們之前以及在調(diào)用initState之前,框架將State對象與BuildContext關聯(lián)起來茉唉,該關聯(lián)是永久的:State對象永遠不會改變它的BuildContext(但是BuildContext本身可以在控件樹中移動)固蛾。后面講解一下這個BuildContext對象在整個程序中什么角色

State

State的作用有兩點:

  1. 在widget構建的時候可以被同步讀取度陆;
  2. 在widget的生命周期中可能會被改變艾凯。

State的生命周期有四種狀態(tài):

  • created:當State對象被創(chuàng)建時候,State.initState方法會被調(diào)用懂傀;
  • initialized:當State對象被創(chuàng)建趾诗,但還沒有準備構建時,State.didChangeDependencies在這個時候會被調(diào)用蹬蚁;
  • ready:State對象已經(jīng)準備好了構建恃泪,State.dispose沒有被調(diào)用的時候;
  • defunct:State.dispose被調(diào)用后犀斋,State對象不能夠被構建贝乎。
圖片4.png

完整生命周期如下:

  • 創(chuàng)建一個State對象時,會調(diào)用StatefulWidget.createState叽粹;
  • 和一個BuildContext相關聯(lián)览效,可以認為被加載了(mounted);
  • 調(diào)用initState虫几;
  • 調(diào)用didChangeDependencies锤灿;
  • 經(jīng)過上述步驟,State對象被完全的初始化了辆脸,調(diào)用build但校;
  • 如果有需要,會調(diào)用didUpdateWidget啡氢;
  • 如果處在開發(fā)模式状囱,熱加載會調(diào)用reassemble;
  • 如果它的子樹(subtree)包含需要被移除的State對象倘是,會調(diào)用deactivate亭枷;
  • 調(diào)用dispose,State對象以后都不會被構建;
  • 當調(diào)用了dispose,State對象處于未加載(unmounted)辨绊,已經(jīng)被dispose的State 對象沒有辦法被重新加載(remount)奶栖。

當控件的配置被更改時會調(diào)用State.didUpdateWidget方法匹表,此時框架會重新繪制控件门坷。你也可以使用State.setState方法在狀態(tài)發(fā)生變化時通知框架宣鄙,告訴框架該對象的內(nèi)部狀態(tài)已經(jīng)改變,框架接到通知后也會重新繪制控件默蚌。

State常用方法
  • initState()
    initState()方法是創(chuàng)建State對象后要調(diào)用的第一個方法(在構造函數(shù)之后)冻晤。
    需要執(zhí)行其他初始化時,將覆蓋重寫此方法绸吸。典型的初始化與動畫鼻弧,控制器有關…
    如果重寫此方法,則需要在第一個位置調(diào)用super.initState()方法锦茁。
    在這個方法中攘轩,上下文context可用,但你還不能真正使用它码俩,因為框架還沒有完全將狀態(tài)與它相關聯(lián)度帮。
    initState()方法完成后,State對象現(xiàn)在已初始化稿存,上下文可用笨篷。在此State對象的生命周期內(nèi)不再調(diào)用此方法。

  • didChangeDependencies()
    didChangeDependencies()方法是要調(diào)用的第二個方法瓣履。
    在此階段率翅,由于上下文可用,您可以使用它袖迎。
    如果您的Widget鏈接到InheritedWidget和/或您需要初始化一些偵聽器(基于BuildContext)冕臭,則通常會覆蓋此方法。請注意瓢棒,如果您的窗口小部件鏈接到InheritedWidget浴韭,則每次重建此窗口小部件時都會調(diào)用此方法。

  • build()
    build(BuildContext context)方法在didChangeDependencies()(和didUpdateWidget)之后調(diào)用脯宿。
    這是您構建窗口小部件(可能還有任何子樹)的位置念颈。
    每次State對象更改時(或者當InheritedWidget需要通知“已注冊”的小部件時)都會調(diào)用此方法!
    為了強制重建连霉,您可以調(diào)用setState((){...})方法榴芳。

  • dispose()
    放棄窗口小部件時調(diào)用dispose()方法。
    如果你需要執(zhí)行一些清理(例如監(jiān)聽器跺撼,控制器…)窟感,然后立即調(diào)用super.dispose(),則覆蓋此方法歉井。

State.setState

State中比較重要的一個方法是setState柿祈,當修改狀態(tài)時,widget會被更新。比方說點擊CheckBox躏嚎,會出現(xiàn)選中和非選中狀態(tài)之間的切換蜜自,就是通過修改狀態(tài)來達到的。查看setState源碼卢佣,在一些異常的情況下將會拋出異常:

  • 傳入的為null重荠;
  • 處在defunct階段;
  • created階段還沒有被加載(mounted)虚茶;
  • 參數(shù)返回一個Future對象戈鲁。
    檢查完一系列異常后,最后調(diào)用代碼如下:
_element.markNeedsBuild();

markNeedsBuild內(nèi)部嘹叫,則是通過標記element為diry婆殿,在下一幀的時候重建(rebuild)≌稚龋可以看出setState并不是立即生效鸣皂,它只是將widget進行了標記,真正的rebuild操作暮蹂,則是等到下一幀的時候才會去進行寞缝。

StatefulWidget的兩個主要類別:

  1. 在initState中創(chuàng)建資源,在dispose中銷毀仰泻,但是不依賴于InheritedWidget或者調(diào)用setState方法荆陆,這類widget基本上用在一個應用或者頁面的root;
  2. 使用setState或者依賴于InheritedWidget集侯,這種在營業(yè)生命周期中會被重建(rebuild)很多次被啼。
    總結一句話就是在每一幀的繪制過程,StatefulWidget會根據(jù)State或者配置變了來rebuild下一幀棠枉,也就是上面圖所說的弄臟了和下一幀又干凈了浓体。
選擇無狀態(tài)還是有狀態(tài)小部件?

在我的小部件的生命周期中辈讶,我是否需要考慮一個將要更改的變量命浴,何時更改,將強制重建小部件贱除?
如果問題的答案是肯定的生闲,那么您需要一個有狀態(tài)的小部件,否則月幌,您需要一個無狀態(tài)小部件碍讯。
比如:

  • 用于顯示復選框列表的小組件。要顯示復選框扯躺,您需要考慮一系列項目捉兴。每個項目都是一個具有標題和狀態(tài)的對象蝎困。如果單擊復選框,則切換相應的item.status;在這種情況下倍啥,您需要使用有狀態(tài)窗口小部件來記住項目的狀態(tài)难衰,以便能夠重繪復選框。
  • 帶有表格的屏幕逗栽。該屏幕允許用戶填寫表單的窗口小部件并將表單發(fā)送到服務器。在這種情況下失暂,在這種情況下彼宠,除非您在提交表單之前需要驗證表單或執(zhí)行任何其他操作,否則無狀態(tài)窗口小部件可能就足夠了弟塞。
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凭峡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子决记,更是在濱河造成了極大的恐慌摧冀,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件系宫,死亡現(xiàn)場離奇詭異索昂,居然都是意外死亡,警方通過查閱死者的電腦和手機扩借,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門椒惨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人潮罪,你說我怎么就攤上這事康谆。” “怎么了嫉到?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵沃暗,是天一觀的道長。 經(jīng)常有香客問我何恶,道長孽锥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任细层,我火速辦了婚禮忱叭,結果婚禮上,老公的妹妹穿的比我還像新娘今艺。我一直安慰自己韵丑,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布虚缎。 她就那樣靜靜地躺著撵彻,像睡著了一般钓株。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陌僵,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天轴合,我揣著相機與錄音,去河邊找鬼碗短。 笑死受葛,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的偎谁。 我是一名探鬼主播总滩,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼巡雨!你這毒婦竟也來了闰渔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤铐望,失蹤者是張志新(化名)和其女友劉穎冈涧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體正蛙,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡督弓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了乒验。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咽筋。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖徊件,靈堂內(nèi)的尸體忽然破棺而出奸攻,到底是詐尸還是另有隱情,我是刑警寧澤虱痕,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布睹耐,位于F島的核電站,受9級特大地震影響部翘,放射性物質(zhì)發(fā)生泄漏硝训。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一新思、第九天 我趴在偏房一處隱蔽的房頂上張望窖梁。 院中可真熱鬧,春花似錦夹囚、人聲如沸纵刘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽假哎。三九已至瞬捕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舵抹,已是汗流浹背肪虎。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惧蛹,地道東北人扇救。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像香嗓,于是被迫代替她去往敵國和親迅腔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容