Flutter中Widget 的State业踏、Context 禽炬、 InheritedWidget

本文介紹了Flutter應(yīng)用程序中Widget,State勤家,Context和InheritedWidget的重要概念腹尖。特別注意InheritedWidget,它是最重要且記錄較少的Widget之一伐脖。

難度:初學(xué)者

前言

在Flutter中Widget, State和Context是一個(gè)非常重要的概念桐臊,每一個(gè)開(kāi)發(fā)人員需要充分了解。
但是晓殊,文檔量巨大断凶,并不能總是清楚地解釋這個(gè)概念。

我會(huì)用自己知識(shí)來(lái)解釋這些概念巫俺,本文的真正目的是試圖澄清以下內(nèi)容:

  • Stateful和Stateless Widget的區(qū)別
  • 什么是上下文Context
  • 什么是state以及如何使用它
  • context與state之間的關(guān)系
  • InheritedWidget以及在 Widgets tree中傳播信息的方式
  • rebuild的概念

本文也適用于Medium-Flutter社區(qū)认烁。

第1部分:概念

Widget的概念

Flutter中,幾乎所有東西都是Widget介汹。

Widget視為可視組件(或與應(yīng)用程序的可視方面交互的組件)却嗡。

當(dāng)您需要構(gòu)建與布局直接或間接相關(guān)的任何內(nèi)容時(shí),您正在使用** Widget**嘹承。

Widget Tree的概念

** Widgets**以樹(shù)結(jié)構(gòu)組織竿音。

包含其他Widgets小部件稱為Parent Widget(或Widget container)辉巡。包含在Parent WidgetWidgets部件稱為Children Widgets拍顷。

讓我們用Flutter自動(dòng)生成的基礎(chǔ)應(yīng)用程序來(lái)說(shuō)明這一點(diǎn)。這是簡(jiǎn)化的代碼坪它,僅限于build方法:

@override
Widget build(BuildContext){
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
}

如果我們現(xiàn)在考慮這個(gè)基本示例,我們將獲得以下Widgets樹(shù)結(jié)構(gòu)(限制代碼中存在的Widgets列表):

state_basic_tree.png

語(yǔ)境概念

另一個(gè)重要的概念是Context帝牡。

*context *只是一個(gè)Widget在所有構(gòu)建的Widget的樹(shù)結(jié)構(gòu)中的位置的引用往毡。。

簡(jiǎn)而言之靶溜,將context視為Widgets樹(shù)的一部分开瞭,其中Widget附加到此樹(shù)。

ContextWidget一一對(duì)應(yīng)罩息。

如果一個(gè)Widget A有子Widget嗤详,Widget A的context將成為一級(jí)子Widget Context的Parent Context

讀到這一點(diǎn)瓷炮,很明顯断楷,Context是鏈接的,并且正在組成一個(gè)Context樹(shù)(父子關(guān)系)崭别。

如果我們現(xiàn)在嘗試在上圖中說(shuō)明Context的概念,我們獲得(仍然是一個(gè)非常簡(jiǎn)化的視圖)每種顏色代表一個(gè)Context除了MyApp恐锣,它是不同的):

state_basic_context_tree.png

Context可見(jiàn)性簡(jiǎn)而言之):

某些Widget只能在其自己的Context中或在其父Context中可見(jiàn)茅主。

由子Context中我們很容易找到一個(gè)ancestor(= parent)Widget。

一個(gè)例子是土榴,考慮Scaffold> Center> Column> Text:context.ancestorWidgetOfExactType(Scaffold)=>通過(guò)從Text Context轉(zhuǎn)到樹(shù)結(jié)構(gòu)來(lái)返回第一個(gè)Scaffold诀姚。

從父Context中,也可以找到后代(=子)Widget玷禽,但不建議這樣做(我們稍后會(huì)討論)赫段。

Widget的類型

Widget有兩種類型:

Stateless Widget

這些可視組件中的一些除了它們自己的配置信息之外不依賴于任何其他信息,該信息其直接父級(jí)構(gòu)建時(shí)提供矢赁。

換句話說(shuō)糯笙,這些Widget一旦創(chuàng)建就不必關(guān)心任何變體

這些小部件稱為Stateless Widget撩银。

這種小部件的典型示例可以是Text给涕,Row,Column额获,Container ......其中够庙,在構(gòu)建時(shí),我們只是將一些參數(shù)傳遞給它們抄邀。

參數(shù)可以是裝飾(decoration)耘眨,尺寸(dimensions)甚至其他Widget中的任何內(nèi)容。不要緊境肾。唯一重要的是這個(gè)配置一旦應(yīng)用剔难,在下一個(gè)構(gòu)建過(guò)程之前不會(huì)改變胆屿。

無(wú)狀態(tài)Widget只能在加載/構(gòu)建(loaded/build)時(shí)才繪制一次,這意味著無(wú)法基于任何事件或用戶操作重繪該Widget钥飞。

Stateless Widget生命周期

這是與Stateless Widget相關(guān)的代碼的典型結(jié)構(gòu)莺掠。

如您所見(jiàn),我們可以將一些額外的參數(shù)傳遞給它的構(gòu)造函數(shù)读宙。但是彻秆,請(qǐng)記住,這些參數(shù)將改變?cè)谝院箅A段结闸。

class MyStatelessWidget extends StatelessWidget {

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

    final parameter;

    @override
    Widget build(BuildContext context){
        return new ...
    }
}

即使有另一種方法可以被覆蓋(createElement)唇兑,后者也幾乎不會(huì)被覆蓋。唯一需要被覆蓋的是構(gòu)建(build)桦锄。

這種Stateless Widget的生命周期很簡(jiǎn)單:

  • 初始化
  • 通過(guò)build()渲染

Stateful Widget

其他一些小部件將處理一些在Widget生命周期內(nèi)會(huì)發(fā)生變化的內(nèi)部數(shù)據(jù)扎附。因此,該數(shù)據(jù)變得動(dòng)態(tài)结耀。

此Widget保存的數(shù)據(jù)集在此Widget的生命周期中可能會(huì)有所不同留夜,稱為State

這些窗口小部件稱為Stateful Widget图甜。

此類Widget的示例可以是用戶可以選擇的復(fù)選框列表碍粥,也可以是根據(jù)條件禁用的Button。

State概念

一個(gè)** State**界定的“ 行為一部份” StatefulWidget實(shí)例黑毅。

它包含旨在與Widget 交互/干擾的信息:

  • 行為
  • 布局

應(yīng)用于State的任何更改都會(huì)強(qiáng)制Widget 重建嚼摩。

State與Context之間的關(guān)系

對(duì)于Stateful WidgetStateContext相關(guān)聯(lián)矿瘦。此關(guān)聯(lián)是永久性的枕面,State對(duì)象永遠(yuǎn)不會(huì)更改其Context

即使可以在樹(shù)結(jié)構(gòu)周?chē)苿?dòng)Widget Context缚去,State仍將與該Context相關(guān)聯(lián)潮秘。

當(dāng)StateContext關(guān)聯(lián)時(shí)State被視為已掛載(mounted)易结。

超重要點(diǎn)

Context相關(guān)聯(lián)的State對(duì)象唇跨,State對(duì)象不能(直接)通過(guò)另一個(gè)Context來(lái)訪問(wèn)!(我們將在稍后討論這個(gè)問(wèn)題)衬衬。


Stateful Widget生命周期

既然已經(jīng)引入了基本概念买猖,那么現(xiàn)在是時(shí)候深入了解......

這是與Stateful Widget相關(guān)的典型代碼結(jié)構(gòu)。

由于本文的主要目的是用“變量(variable)”數(shù)據(jù)來(lái)解釋State的概念滋尉,我將故意跳過(guò)與某些Stateful Widget overridable方法相關(guān)的任何解釋玉控,這些方法與此沒(méi)有特別的關(guān)系。這些可* overridable方法是didUpdateWidget狮惜,deactivate高诺,reassemble*碌识。這些將在下一篇文章中討論。

class MyStatefulWidget extends StatefulWidget {

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

    final parameter;

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

class _MyStatefulWidgetState extends State<MyStatefulWidget> {

    @override
    void initState(){
        super.initState();

        // Additional initialization of the State
    }

    @override
    void didChangeDependencies(){
        super.didChangeDependencies();

        // Additional code
    }

    @override
    void dispose(){
        // Additional disposal code

        super.dispose();
    }

    @override
    Widget build(BuildContext context){
        return new ...
    }
}

下圖顯示了與創(chuàng)建Stateful Widget相關(guān)的操作/調(diào)用序列(簡(jiǎn)化版本)虱而。在圖的右側(cè)筏餐,您將注意到流中的State對(duì)象的內(nèi)部狀態(tài)。您還將看到Context與State關(guān)聯(lián)的時(shí)刻牡拇,從而變?yōu)榭捎茫╩ounted)魁瞪。

state_diagram.png

所以讓我們用一些額外的細(xì)節(jié)來(lái)解釋它:

initState()

initState()方法是第一個(gè)方法(構(gòu)造函數(shù)后面),一旦State對(duì)象被創(chuàng)建被調(diào)用惠呼。需要執(zhí)行其他初始化時(shí)导俘,將覆蓋此方法。典型的初始化與動(dòng)畫(huà)剔蹋,控制器有關(guān)......如果重寫(xiě)此方法旅薄,則需要首先調(diào)用super.initState()方法。

在這個(gè)方法中泣崩,Context可用但你還不能真正使用它少梁,因?yàn)榭蚣苓€沒(méi)有完全將狀態(tài)與它相關(guān)聯(lián)。

一旦initState()方法完成矫付,State對(duì)象現(xiàn)在被初始化并且Context可用凯沪。

在此State對(duì)象的生命周期內(nèi)不再調(diào)用此方法。

didChangeDependencies()

所述didChangeDependencies()方法是將被調(diào)用的第二方法技即。

在此階段,由于Context可用樟遣,您可以使用它而叼。

如果您的Widget鏈接到InheritedWidget和/或您需要初始化某些監(jiān)聽(tīng)(listeners)(基于Context),則通常會(huì)覆蓋此方法豹悬。

請(qǐng)注意葵陵,如果您的窗口小部件鏈接到InheritedWidget,則每次重建此Widget時(shí)都會(huì)調(diào)用此方法瞻佛。

如果重寫(xiě)此方法脱篙,則應(yīng)首先調(diào)用super.didChangeDependencies()

build()

build(BuildContext context)方法在didChangeDependencies() (和didUpdateWidget)之后調(diào)用伤柄。

這是您構(gòu)建Widget(可能還有任何子樹(shù))的地方绊困。

每次State對(duì)象更改時(shí)(或者當(dāng)InheritedWidget需要通知“ 已注冊(cè) ”的Widget時(shí))都會(huì)調(diào)用此方法!

為了強(qiáng)制重建适刀,您可以調(diào)用*setState((){…}) *方法秤朗。

dispose()

dispose()方法在Widget被銷(xiāo)毀時(shí)調(diào)用。

如果需要執(zhí)行一些清理(例如監(jiān)聽(tīng)器)笔喉,則重寫(xiě)此方法取视,然后立即調(diào)用super.dispose()硝皂。

Stateless or Stateful Widget?

這是許多開(kāi)發(fā)人員需要問(wèn)自己的問(wèn)題:我是否需要我的Widget無(wú)狀態(tài)或有狀態(tài)作谭?

為了回答這個(gè)問(wèn)題稽物,請(qǐng)問(wèn)問(wèn)自己:

在我的Widget的生命周期中,我是否需要考慮一個(gè)將要更改的變量折欠,何時(shí)更改贝或,將強(qiáng)制** rebuilt**Widget?

如果問(wèn)題的答案是肯定的怨酝,那么您需要一個(gè)有狀態(tài)Widget傀缩,否則,您需要一個(gè)無(wú)狀態(tài)Widget农猬。

一些例子:

  • 用于顯示復(fù)選框列表的Widget赡艰。要顯示復(fù)選框,您需要考慮一系列項(xiàng)目斤葱。每個(gè)項(xiàng)目都是一個(gè)具有標(biāo)題和狀態(tài)的對(duì)象慷垮。如果單擊復(fù)選框,則切換相應(yīng)的item.status;

    在這種情況下揍堕,您需要使用StatefulWidget來(lái)記住項(xiàng)目的狀態(tài)料身,以便能夠重繪復(fù)選框。

  • 帶有表格的屏幕衩茸。該屏幕允許用戶填寫(xiě)表單的Widget并將表單發(fā)送到服務(wù)器芹血。

    在這種情況下,除非你需要驗(yàn)證表或提交之前楞慈,做任何其他動(dòng)作幔烛,一個(gè)StatelessWidget可能就足夠了。


Stateful Widget由2部分組成

還記得Stateful小部件的結(jié)構(gòu)嗎囊蓝?有兩個(gè)部分:

Widget的主要定義

class MyStatefulWidget extends StatefulWidget {
    MyStatefulWidget({
        Key key,
        this.color,
    }): super(key: key);

    final Color color;

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

第一部分“ MyStatefulWidget通常是Widget 的公共部分饿悬。當(dāng)您要將其添加到窗口小部件樹(shù)時(shí),可以實(shí)例化此部件聚霜。此部分在Widget的生命周期內(nèi)不會(huì)發(fā)生變化狡恬,但可能接受可能由其相應(yīng)的State實(shí)例使用的參數(shù)。

請(qǐng)注意蝎宇,在Widget的第一部分級(jí)別定義的任何變量通常 不會(huì)在其生命周期內(nèi)發(fā)生變化弟劲。

Widget State定義

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
    ...
    @override
    Widget build(BuildContext context){
        ...
    }
}

第二部分“ _MyStatefulWidgetState ”是在Widget的生命周期中變化的部分,并強(qiáng)制每次應(yīng)用修改時(shí)重建Widget的這個(gè)特定實(shí)例姥芥。名稱開(kāi)頭的“ _ ”字符使該類對(duì).dart文件是私有的函卒。

如果需要在.dart文件之外引用此類,請(qǐng)不要使用“ _ ”前綴。

所述_MyStatefulWidgetState類可以訪問(wèn)被存儲(chǔ)在任何可變MyStatefulWidget报嵌,使用Widget虱咧。{變量名稱}。在此示例中:widget.color


Widget唯一標(biāo)識(shí)Key

在Flutter中锚国,每個(gè)Widget都是唯一標(biāo)識(shí)的腕巡。這個(gè)唯一標(biāo)識(shí)由構(gòu)建/渲染時(shí)的框架定義。

此唯一標(biāo)識(shí)對(duì)應(yīng)于可選的Key參數(shù)血筑。如果省略绘沉,F(xiàn)lutter將為您生成一個(gè)。

在某些情況下豺总,您可能需要強(qiáng)制使用此Key车伞,以便可以通過(guò)其key訪問(wèn)Widget。

為此喻喳,您可以使用以下幫助程序之一:GlobalKey另玖,LocalKeyUniqueKeyObjectKey表伦。

GlobalKey確保關(guān)鍵是在整個(gè)應(yīng)用程序唯一的谦去。

強(qiáng)制使用Widget的唯一標(biāo)識(shí):

    GlobalKey myKey = new GlobalKey();
    ...
    @override
    Widget build(BuildContext context){
        return new MyWidget(
            key: myKey
        );
    }

第2部分:如何訪問(wèn)State?

如前所述蹦哼,State鏈接到一個(gè)Context鳄哭,Context鏈接到Widget的一個(gè)實(shí)例對(duì)象

1. Widget本身

從理論上講纲熏,唯一能夠訪問(wèn)StateWidget State本身妆丘。

在這種情況下,沒(méi)有困難局劲。Widget State類訪問(wèn)其任何變量勺拣。

2.一個(gè)直接的子Widget

有時(shí),父Widget可能需要訪問(wèn)其直接子節(jié)點(diǎn)的State才能執(zhí)行特定任務(wù)容握。

在這種情況下宣脉,要訪問(wèn)這些直接的子State车柠,您需要了解它們剔氏。

給某人打電話的最簡(jiǎn)單方法是通過(guò)名字。在Flutter中竹祷,每個(gè)Widget都有一個(gè)唯一的標(biāo)識(shí)谈跛,它由框架在構(gòu)建/渲染時(shí)確定。如前所示塑陵,您可以使用key參數(shù)強(qiáng)制使用Widget的標(biāo)識(shí)感憾。

    ...
    GlobalKey<MyStatefulWidgetState> myWidgetStateKey = new GlobalKey<MyStatefulWidgetState>();
    ...
    @override
    Widget build(BuildContext context){
        return new MyStatefulWidget(
            key: myWidgetStateKey,
            color: Colors.blue,
        );
    }

一旦確定StateKey, Widget可以通過(guò)以下方式訪問(wèn)其子級(jí)的State

myWidgetStateKey.currentState

讓我們考慮一個(gè)基本示例令花,當(dāng)用戶點(diǎn)擊按鈕時(shí)顯示SnackBar阻桅。由于SnackBar是Scaffold的子Widget凉倚,它不能直接訪問(wèn)Scaffold身體的任何其他孩子(請(qǐng)記住Context的概念及其層次結(jié)構(gòu)/樹(shù)結(jié)構(gòu)?)嫂沉。因此稽寒,訪問(wèn)它的唯一方法是通過(guò)ScaffoldState,它公開(kāi)一個(gè)公共方法來(lái)顯示SnackBar趟章。

    class _MyScreenState extends State<MyScreen> {
        /// the unique identity of the Scaffold
        final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

        @override
        Widget build(BuildContext context){
            return new Scaffold(
                key: _scaffoldKey,
                appBar: new AppBar(
                    title: new Text('My Screen'),
                ),
                body: new Center(
                    new RaiseButton(
                        child: new Text('Hit me'),
                        onPressed: (){
                            _scaffoldKey.currentState.showSnackBar(
                                new SnackBar(
                                    content: new Text('This is the Snackbar...'),
                                )
                            );
                        }
                    ),
                ),
            );
        }
    }

3.Ancestor Widget

假設(shè)您有一個(gè)屬于另一個(gè)Widget的子樹(shù)的Widget杏糙,如下圖所示。

state_child_get_state.png

為了實(shí)現(xiàn)這一目標(biāo)蚓土,需要滿足3個(gè)條件:

1.“ Widget with State ”(紅色)需要暴露其State

為了公開(kāi)它的State宏侍,Widget需要在創(chuàng)建時(shí)記錄它,如下所示:

class MyExposingWidget extends StatefulWidget {

   MyExposingWidgetState myState;

   @override
   MyExposingWidgetState createState(){
      myState = new MyExposingWidgetState();
      return myState;
   }
}

2.“ Widget State ”需要暴露一些getter / setter

為了讓“ * stranger* ” set/get State的屬性蜀漆,Widget State需要通過(guò)以下方式授權(quán)訪問(wèn):

  • public property(不推薦)
  • getter / setter

例:

class MyExposingWidgetState extends State<MyExposingWidget>{
   Color _color;

   Color get color => _color;
   ...
}

3.“ 對(duì)于想要獲取State對(duì)象的Widget ”(藍(lán)色)需要獲得對(duì)State對(duì)象的的引用

class MyChildWidget extends StatelessWidget {
   @override
   Widget build(BuildContext context){
      final MyExposingWidget widget = context.ancestorWidgetOfExactType(MyExposingWidget);
      final MyExposingWidgetState state = widget?.myState;

      return new Container(
         color: state == null ? Colors.blue : state.color,
      );
   }
}

這個(gè)解決方案很容易實(shí)現(xiàn)谅河,但子Widget如何知道它何時(shí)需要重建?

有了這個(gè)解決方案嗜愈,它沒(méi)有旧蛾。它必須等待重建才能刷新其內(nèi)容,這不是很方便蠕嫁。

下一節(jié)將討論Inherited Widget的概念锨天,它可以解決這個(gè)問(wèn)題。


InheritedWidget

簡(jiǎn)而言之剃毒,InheritedWidget允許在Widget樹(shù)中有效地傳播(和共享)信息病袄。

InheritedWidget是一個(gè)特殊的Widget,您將在widgets樹(shù)中作為另一個(gè)子樹(shù)的父Widget赘阀。該子樹(shù)的所有Widget都必須能夠與該InheritedWidget公開(kāi)的數(shù)據(jù)進(jìn)行交互益缠。

Basics

為了解釋它,讓我們考慮以下代碼:

class MyInheritedWidget extends InheritedWidget {
   MyInheritedWidget({
      Key key,
      @required Widget child,
      this.data,
   }): super(key: key, child: child);

   final data;

   static MyInheritedWidget of(BuildContext context) {
      return context.inheritFromWidgetOfExactType(MyInheritedWidget);
   }

   @override
   bool updateShouldNotify(MyInheritedWidget oldWidget) => data != oldWidget.data;
}

此代碼定義了一個(gè)名為“ MyInheritedWidget ” 的Widget基公,旨在“ 共享 ”所有Widget(子子樹(shù)的一部分)中的某些數(shù)據(jù)幅慌。

如前所述,為了能夠傳播/共享某些數(shù)據(jù)轰豆,需要將InheritedWidget定位在Widget的頂部胰伍,這解釋了傳遞給InheritedWidget基礎(chǔ)構(gòu)造函數(shù)的@required Widget child

static MyInheritedWidget of(BuildContext context)方法酸休,允許所有Widget獲取最接近Context的MyInheritedWidget的實(shí)例(參見(jiàn)后面的內(nèi)容)骂租。

最后,重寫(xiě)updateShouldNotify方法用于告訴InheritedWidget斑司,如果對(duì)數(shù)據(jù)應(yīng)用了修改(請(qǐng)參閱下文)渗饮,是否必須將通知傳遞給所有子Widget(已注冊(cè)/已訂閱)。

因此,我們需要將它放在樹(shù)節(jié)點(diǎn)級(jí)別互站,如下所示:

class MyParentWidget... {
   ...
   @override
   Widget build(BuildContext context){
      return new MyInheritedWidget(
         data: counter,
         child: new Row(
            children: <Widget>[
               ...
            ],
         ),
      );
   }
}

子Widget如何訪問(wèn)InheritedWidget的數(shù)據(jù)私蕾?

在構(gòu)建子進(jìn)程時(shí),后者將獲得對(duì)InheritedWidget的引用胡桃,如下所示:

class MyChildWidget... {
   ...

   @override
   Widget build(BuildContext context){
      final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);

      ///
      /// From this moment, the widget can use the data, exposed by the MyInheritedWidget
      /// by calling:  inheritedWidget.data
      ///
      return new Container(
         color: inheritedWidget.data.color,
      );
   }
}

如何在Widget之間進(jìn)行交互是目?

請(qǐng)考慮以下顯示W(wǎng)idget樹(shù)結(jié)構(gòu)的圖表。


WX20190730-155539@2x.png

為了說(shuō)明一種交互方式标捺,我們假設(shè)如下:

  • “Widget A”是一個(gè)將項(xiàng)目添加到購(gòu)物車(chē)的按鈕;
  • “Widget B”是一個(gè)顯示購(gòu)物車(chē)中商品數(shù)量的文本;
  • “Widget C”位于小部件B旁邊懊纳,是一個(gè)內(nèi)置任何文本的文本;
  • 我們希望“Widget B”在按下“Widget A”時(shí)自動(dòng)在購(gòu)物車(chē)中顯示正確數(shù)量的項(xiàng)目,但我們不希望重建“Widget C”

InheritedWidget在此是最為合適的Widget亡容!

代碼示例

我們先寫(xiě)下代碼嗤疯,然后解釋如下:

class Item {
   String reference;

   Item(this.reference);
}

class _MyInherited extends InheritedWidget {
  _MyInherited({
    Key key,
    @required Widget child,
    @required this.data,
  }) : super(key: key, child: child);

  final MyInheritedWidgetState data;

  @override
  bool updateShouldNotify(_MyInherited oldWidget) {
    return true;
  }
}

class MyInheritedWidget extends StatefulWidget {
  MyInheritedWidget({
    Key key,
    this.child,
  }): super(key: key);

  final Widget child;

  @override
  MyInheritedWidgetState createState() => new MyInheritedWidgetState();

  static MyInheritedWidgetState of(BuildContext context){
    return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }
}

class MyInheritedWidgetState extends State<MyInheritedWidget>{
  /// List of Items
  List<Item> _items = <Item>[];

  /// Getter (number of items)
  int get itemsCount => _items.length;

  /// Helper method to add an Item
  void addItem(String reference){
    setState((){
      _items.add(new Item(reference));
    });
  }

  @override
  Widget build(BuildContext context){
    return new _MyInherited(
      data: this,
      child: widget.child,
    );
  }
}

class MyTree extends StatefulWidget {
  @override
  _MyTreeState createState() => new _MyTreeState();
}

class _MyTreeState extends State<MyTree> {
  @override
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      child: new Scaffold(
        appBar: new AppBar(
          title: new Text('Title'),
        ),
        body: new Column(
          children: <Widget>[
            new WidgetA(),
            new Container(
              child: new Row(
                children: <Widget>[
                  new Icon(Icons.shopping_cart),
                  new WidgetB(),
                  new WidgetC(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item');
        },
      ),
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Text('${state.itemsCount}');
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Text('I am Widget C');
  }
}

說(shuō)明

在這個(gè)非常基本的例子中闺兢,

  • _MyInherited是一個(gè)InheritedWidget茂缚,每次我們通過(guò)點(diǎn)擊“Widget A”按鈕添加一個(gè)Item時(shí)都會(huì)重新創(chuàng)建
  • MyInheritedWidget是一個(gè)Widget,其狀態(tài)包含Items列表屋谭〗拍遥可以通過(guò)(BuildContext context)靜態(tài)MyInheritedWidgetState訪問(wèn)此狀態(tài)
  • MyInheritedWidgetState公開(kāi)一個(gè)getter(itemsCount)和一個(gè)方法(addItem),以便它們可以被小部件使用桐磁,這是小部件樹(shù)的一部分
  • 每次我們將一個(gè)Item添加到State時(shí)悔耘,MyInheritedWidgetState都會(huì)重建
  • MyTree類只是構(gòu)建一個(gè)小部件樹(shù),將MyInheritedWidget作為樹(shù)的父級(jí)
  • WidgetA是一個(gè)簡(jiǎn)單的RaisedButton我擂,當(dāng)按下它時(shí)衬以,從最近的MyInheritedWidget調(diào)用addItem方法
  • WidgetB是一個(gè)簡(jiǎn)單的文本,顯示最接近的 MyInheritedWidget級(jí)別的項(xiàng)目數(shù)

這一切如何運(yùn)作校摩?

注冊(cè)Widget以供以后通知

當(dāng)子Widget調(diào)用MyInheritedWidget.of(context)時(shí)看峻,傳遞它自己的context,它調(diào)用MyInheritedWidget的以下方法衙吩。

  static MyInheritedWidgetState of(BuildContext context) {
    return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }

在內(nèi)部互妓,除了簡(jiǎn)單地返回MyInheritedWidgetState的實(shí)例之外,它還將將通知傳遞給所有子Widget(已注冊(cè)/已訂閱)坤塞。

在場(chǎng)景后面冯勉,對(duì)這個(gè)靜態(tài)方法的簡(jiǎn)單調(diào)用實(shí)際上做了兩件事:

  • 消費(fèi)者(consumer)Widget被自動(dòng)添加到(subscribers)列表中,之后如果InheritedWidget(這里_MyInherited)數(shù)據(jù)被修改尺锚,會(huì)被重建
  • _MyInheritedWidget(又名MyInheritedWidgetState)中引用的數(shù)據(jù)將返回給使用者(consumer)

由于'Widget A'和'Widget B'都已使用InheritedWidget訂閱珠闰,因此如果對(duì)_MyInherited應(yīng)用了修改惜浅,則當(dāng)單擊Widget A 的RaisedButton時(shí)瘫辩,操作流程如下(簡(jiǎn)化版本):

  1. 調(diào)用MyInheritedWidgetStateaddItem方法
  2. MyInheritedWidgetState.addItem方法將新項(xiàng)添加到List
  3. 調(diào)用setState()以重建MyInheritedWidget
  4. 使用List的新內(nèi)容創(chuàng)建_MyInherited的新實(shí)例
  5. _MyInherited記錄在參數(shù)(數(shù)據(jù))中傳遞的新State
  6. 作為InheritedWidget,它會(huì)檢查是否有需要通知消費(fèi)者(答案為真)
  7. 它迭代整個(gè)消費(fèi)者列表(這里是Widget A和Widget B)并請(qǐng)求他們重建
  8. 由于Wiget C不是消費(fèi)者,因此不會(huì)重建伐厌。

但是承绸,Widget A和Widget B都重建了,而重建Wiget A卻沒(méi)用挣轨,因?yàn)樗鼪](méi)有任何改變军熏。如何防止這種情況發(fā)生?

訪問(wèn)“Inherited Widget”Widget時(shí)阻止某些Widget重建

Widget A也被重建的原因來(lái)自它訪問(wèn)MyInheritedWidgetState的方式卷扮。

如前所述荡澎,調(diào)用context.inheritFromWidgetOfExactType()方法的事實(shí)會(huì)自動(dòng)將Widget訂閱到使用者列表中。

該解決方案晤锹,以防止該自動(dòng)訂閱摩幔,同時(shí)仍然允許該Widget A訪問(wèn)MyInheritedWidgetState是改變的靜態(tài)方法MyInheritedWidget如下:

  static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
    return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
                    : context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }

通過(guò)添加布爾額外參數(shù)...

  • 如果rebuild參數(shù)為true(默認(rèn)情況下),我們使用普通方法(并且Widget將添加到訂閱者列表中)
  • 如果rebuild參數(shù)是假的鞭铆,我們?nèi)匀豢梢栽L問(wèn)數(shù)據(jù)或衡,但不使用內(nèi)部實(shí)現(xiàn)的的InheritedWidget

因此,要完成解決方案车遂,我們還需要稍微更新Widget A的代碼封断,如下所示(我們添加false額外參數(shù)):

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context, false);
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item');
        },
      ),
    );
  }
}

它就是,按下它時(shí)不再重建Widget A.

特別注意Routes, Dialogs…

Routes, Dialog Context與應(yīng)用程序綁定舶担。

這意味著即使在屏幕A內(nèi)部您要求顯示另一個(gè)屏幕B(例如坡疼,在當(dāng)前的屏幕上),也無(wú)法輕松地從兩個(gè)屏幕中的任何一個(gè)屏幕關(guān)聯(lián)它們自己的Context衣陶。

屏幕B了解屏幕A上下文的唯一方法是從屏幕A獲取它作為Navigator.of(context).push(...)的參數(shù)回梧。

參考鏈接

結(jié)論

關(guān)于這些主題還有很多話要說(shuō)......特別是在InheritedWidget上

在下一篇文章中祖搓,我將介紹通知器/監(jiān)聽(tīng)器的概念狱意,這在使用State和傳送數(shù)據(jù)的方式中也非常有趣。


翻譯不容易拯欧,大家且看且珍惜
原文

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末详囤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子镐作,更是在濱河造成了極大的恐慌藏姐,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,002評(píng)論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件该贾,死亡現(xiàn)場(chǎng)離奇詭異羔杨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)杨蛋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門(mén)兜材,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)理澎,“玉大人,你說(shuō)我怎么就攤上這事曙寡】放溃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,787評(píng)論 0 365
  • 文/不壞的土叔 我叫張陵举庶,是天一觀的道長(zhǎng)执隧。 經(jīng)常有香客問(wèn)我,道長(zhǎng)户侥,這世上最難降的妖魔是什么镀琉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,237評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮蕊唐,結(jié)果婚禮上滚粟,老公的妹妹穿的比我還像新娘。我一直安慰自己刃泌,他們只是感情好凡壤,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著耙替,像睡著了一般亚侠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俗扇,一...
    開(kāi)封第一講書(shū)人閱讀 52,821評(píng)論 1 314
  • 那天硝烂,我揣著相機(jī)與錄音,去河邊找鬼铜幽。 笑死滞谢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的除抛。 我是一名探鬼主播狮杨,決...
    沈念sama閱讀 41,236評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼到忽!你這毒婦竟也來(lái)了橄教?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,196評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤喘漏,失蹤者是張志新(化名)和其女友劉穎护蝶,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體翩迈,經(jīng)...
    沈念sama閱讀 46,716評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡持灰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了负饲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堤魁。...
    茶點(diǎn)故事閱讀 40,928評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喂链,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姨涡,到底是詐尸還是另有隱情,我是刑警寧澤吧慢,帶...
    沈念sama閱讀 36,583評(píng)論 5 351
  • 正文 年R本政府宣布涛漂,位于F島的核電站,受9級(jí)特大地震影響检诗,放射性物質(zhì)發(fā)生泄漏匈仗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評(píng)論 3 336
  • 文/蒙蒙 一逢慌、第九天 我趴在偏房一處隱蔽的房頂上張望悠轩。 院中可真熱鬧,春花似錦攻泼、人聲如沸火架。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,755評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)何鸡。三九已至,卻和暖如春牛欢,著一層夾襖步出監(jiān)牢的瞬間骡男,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,869評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工傍睹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留隔盛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,378評(píng)論 3 379
  • 正文 我出身青樓拾稳,卻偏偏與公主長(zhǎng)得像吮炕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子访得,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評(píng)論 2 361