BuildContext與InheritedWidget

在Flutter中Widjet,State,BuildContext,InheritedWidget在整個(gè)小部件里都是非常重要的概念。如何自定義,如何使用都需要對(duì)這幾個(gè)概念十分了解。

BuildContext

BuildContext只不過(guò)是對(duì)構(gòu)建的所有窗口小部件的樹(shù)結(jié)構(gòu)中的窗口小部件的位置的引用。簡(jiǎn)而言之,將BuildContext視為Widgets樹(shù)的一部分,Widget將附加到此樹(shù)。一個(gè)BuildContext只屬于一個(gè)小部件。
如果窗口小部件“A”具有子窗口小部件,則窗口小部件“A”的BuildContext將成為直接子窗口BuildContexts的父BuildContext。很明顯BuildContexts是鏈接的,并且正在組成BuildContexts樹(shù)(父子關(guān)系)。

BuildContext可見(jiàn)性(簡(jiǎn)化語(yǔ)句):
“ Something ”僅在其自己的BuildContext或其父BuildContext的BuildContext中可見(jiàn)。

比如一個(gè)例子是,考慮Scaffold> Center> Column> Text,我希望從最后面的Text找到最頂端的Widjet:

context.ancestorWidgetOfExactType(Scaffold)=>通過(guò)從Text上下文轉(zhuǎn)到樹(shù)結(jié)構(gòu)來(lái)返回第一個(gè)Scaffold。

從父BuildContext,也可以找到一個(gè)后代(=子)Widget,但不建議這樣做
在上文總結(jié)了State,那么State與BuildContext有啥關(guān)系呢?

State和BuildContext之間的關(guān)系

對(duì)于有狀態(tài)窗口小部件,狀態(tài)與BuildContext關(guān)聯(lián)。此關(guān)聯(lián)是永久性的 ,State對(duì)象永遠(yuǎn)不會(huì)更改其BuildContext。即使可以在樹(shù)結(jié)構(gòu)周圍移動(dòng)Widget BuildContext,State仍將與該BuildContext保持關(guān)聯(lián)。當(dāng)State與BuildContext關(guān)聯(lián)時(shí),State被視為已掛載。

由于State對(duì)象與BuildContext相關(guān)聯(lián),這意味著State對(duì)象不能(直接)通過(guò)另一個(gè)BuildContext訪問(wèn)!

State鏈接到一個(gè)BuildContext,BuildContext鏈接到Widget的一個(gè)實(shí)例。

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)制使用此密鑰,以便可以通過(guò)其密鑰訪問(wèn)窗口小部件。為此,您可以使用以下幫助程序之一:GlobalKey ,LocalKey,UniqueKey 或ObjectKey。

// 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
        );
    }

有時(shí),父窗口小部件可能需要訪問(wèn)其直接子節(jié)點(diǎn)的狀態(tài)才能執(zhí)行特定任務(wù)。在這種情況下,要訪問(wèn)這些直接子項(xiàng)State,您需要了解它們。我們先看看以下列子:讓我們考慮一個(gè)基本示例,當(dāng)用戶點(diǎn)擊按鈕時(shí)顯示SnackBar。由于SnackBar是Scaffold的子Widget,它不能直接被Scaffold身體的任何其他孩子訪問(wèn)(還記得上下文的概念及其層次結(jié)構(gòu)/樹(shù)結(jié)構(gòu)嗎?)。因此,訪問(wèn)它的唯一方法是通過(guò)ScaffoldState,它公開(kāi)一個(gè)公共方法來(lái)顯示SnackBar。

import 'package:flutter/material.dart';

class SnackBarDemo extends StatefulWidget{

  @override
  State<StatefulWidget> createState() {
    return new SnackState();
  }

}

class SnackState extends State<SnackBarDemo>{

  final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

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

}
InheritedWidget

在說(shuō)這個(gè)部件的時(shí)候,先來(lái)看看這個(gè)一個(gè)需求:如何在子Widget中獲取到另外一個(gè)Widget的state的屬性?如下代碼:

import 'package:flutter/material.dart';

class OneWidget extends StatefulWidget {
  OneState one;

  @override
  State<StatefulWidget> createState() {
    one = new OneState();
    return one;
  }
}

class OneState extends State<OneWidget> {
  // 私有 暴露一些getter / setter
  Color _color = Colors.red;

  Color get color => _color;

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new Tow(),
    );
  }
}

class Tow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final OneWidget widget = context.ancestorWidgetOfExactType(OneWidget);
    final OneState state = widget?.one;
    return new Container(
      color: state == null ?  Colors.blue : state.color,
    );
  }
}

雖然實(shí)現(xiàn)了,但子窗口小部件如何知道它何時(shí)需要重建?在這個(gè)解決方案,它不知道。它必須等待重建才能刷新其內(nèi)容,這不是很方便。下面將討論Inherited Widget的概念,它可以解決這個(gè)問(wèn)題。

InheritedWidget允許在窗口小部件樹(shù)中有效地傳播(和共享)信息:InheritedWidget是一個(gè)特殊的Widget,您可以將其作為另一個(gè)子樹(shù)的父級(jí)放在Widgets樹(shù)中。該子樹(shù)的所有小部件都必須能夠與該InheritedWidget公開(kāi)的數(shù)據(jù)進(jìn)行交互。他的用法其實(shí)在很多系統(tǒng)的API中常見(jiàn)比如:Theme.of(context).textTheme,MediaQuery.of(context).size

class ChildWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
    return new Text('我是測(cè)試',style: Theme.of(context).textTheme.title,);
  }
}

inherited widget就像對(duì)其他的Widget的一個(gè)實(shí)現(xiàn)或者說(shuō)是補(bǔ)充,就像Theme.of(context)返回你能拿到一個(gè)ThemeData,并使用其內(nèi)部自定義的屬性改變你當(dāng)前widget的顯示效果。那么如何體使用?可以看看下面列子:

import 'package:flutter/material.dart';

// 共享的數(shù)據(jù)
class InheritedData{
  final String data;
  const InheritedData(this.data);
}

// MyInheritedWidget作為所有widget的根旨在“共享”所有小部件(與子樹(shù)的一部分)中的某些數(shù)據(jù)。
class MyInheritedWidget extends InheritedWidget {
  InheritedData data; // 共享的數(shù)據(jù)

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

  // 暴露給外部以便獲得MyInheritedWidget,允許所有子窗口小部件獲取最接近上下文的MyInheritedWidget的實(shí)例
  static MyInheritedWidget of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(MyInheritedWidget);
  }

  // 用于告訴InheritedWidget是否必須將通知傳遞給所有子窗口小部件(已注冊(cè)/已訂閱),如果對(duì)數(shù)據(jù)應(yīng)用了修改
  @override
  bool updateShouldNotify(MyInheritedWidget oldWidget) =>
      data != oldWidget.data;
}

class ChildWidget extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    // 子Widget就可以拿到父Widget的數(shù)據(jù)
    final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
    final InheritedData data = inheritedWidget.data;
    print("data的值為:"+data.data);
    return new Text(data.data);
  }
}

// 父容器
class ParentWidget extends StatelessWidget {

  InheritedData data;

  @override
  Widget build(BuildContext context) {
    // 構(gòu)造器對(duì)數(shù)據(jù)容器進(jìn)行初始化操作,保證后面拿到的data不是空
    data = new InheritedData("來(lái)打我啊");
    return new MyInheritedWidget(data: data,child: new ChildWidget());
  }
}

上面InheritedWidget其實(shí)可以看出是一個(gè)公共的數(shù)據(jù)共享空間,之前說(shuō)她還有更新Widget功能,可以看下面示例,點(diǎn)擊+和-兩個(gè)按鈕實(shí)現(xiàn)InheritedWidget中數(shù)據(jù)的增加與減少,并且用一個(gè)Widget顯示InheritedWidget中數(shù)據(jù)的增加情況:

import 'package:flutter/material.dart';

/**
 * 共享的model
 */
class InheritedModel {
  final int count;

  InheritedModel(this.count);
}

/**
 * 定義InheritedWidget
 */
class TestInheritedWidget extends InheritedWidget {
  // 共享的數(shù)據(jù)
  final InheritedModel mode;

  // 自增的方法
  final Function() increment;

  // 自減的方法
  final Function() reduce;


  TestInheritedWidget({
    Key key,
    @required this.mode,
    @required this.increment,
    @required this.reduce,
    @required Widget child,
  }) : super(key: key, child: child);

  //是否重建widget就取決于數(shù)據(jù)是否相同
  @override
  bool updateShouldNotify(TestInheritedWidget oldWidget) =>
      mode != oldWidget.mode;

  static TestInheritedWidget of(BuildContext context) =>
      context.inheritFromWidgetOfExactType(TestInheritedWidget);
}

/**
 * 自增的widget
 */
class IncrementWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final testInheritedWidget = TestInheritedWidget.of(context);
    final mode = testInheritedWidget.mode;
    print('IncrementWidget中的count:${mode.count}');
    return new Padding(
      padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
      child: new RaisedButton(
          onPressed: testInheritedWidget.increment,
          textColor: Colors.red,
          child: new Text("+")),
    );
  }
}


/**
 * 自減的widget
 */
class ReduceWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final testInheritedWidget = TestInheritedWidget.of(context);
    final mode = testInheritedWidget.mode;
    print('ReduceWidget中的count:${mode.count}');
    return new Padding(
      padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
      child: new RaisedButton(
          onPressed: testInheritedWidget.reduce,
          textColor: Colors.green,
          child: new Text("-")),
    );
  }
}

/**
 * 展示數(shù)據(jù)更新的Widget
 */
class ShowCountWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final testInheritedWidget = TestInheritedWidget.of(context);
    final modeData = testInheritedWidget.mode;
    print('ShowCountWidget中的count:${modeData.count}');
    return new Padding(
      padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
      child: new Text(
        '當(dāng)前count:${modeData.count}',
        style: new TextStyle(fontSize: 20.0),
      ),
    );
  }
}

/**
 * 因?yàn)樾枰趕tate中改變不同的增減操作,所以選擇StatefulWidget
 */
class InheritedWidgetTestContainer extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new _InheritedWidgetTestContainerState();
  }
}

class _InheritedWidgetTestContainerState
    extends State<InheritedWidgetTestContainer> {
  InheritedModel model;

  _initData() {
    model = new InheritedModel(0);
  }

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

  _incrementCount() {
    setState(() {
      model = new InheritedModel(model.count + 1);
    });
  }

  _reduceCount() {
    setState(() {
      model = new InheritedModel(model.count - 1);
    });
  }

  @override
  Widget build(BuildContext context) {
    return new TestInheritedWidget(
        mode: model,
        increment: _incrementCount,
        reduce: _reduceCount,
        child: new Scaffold(
          appBar: new AppBar(
            title: new Text('我是測(cè)試widget更新'),
          ),
          body: new Column(
            children: <Widget>[
              new IncrementWidget(),
              new ShowCountWidget(),
              new ReduceWidget(),
            ],
          ),
        )
    );
  }
}

上面的代碼中其實(shí)就是以TestInheritedWidget作為數(shù)據(jù)共享的根節(jié)點(diǎn),在其節(jié)點(diǎn)下的所有Widget在數(shù)據(jù)發(fā)現(xiàn)變化的時(shí)候都會(huì)重新更新。那么我們有一個(gè)需求,就是在一個(gè)節(jié)點(diǎn)下有A,B,C三個(gè)Widget,我希望B在A點(diǎn)擊的時(shí)候狀態(tài)會(huì)改變,但是A,C不改變?nèi)绾螌?shí)現(xiàn):

import 'package:flutter/material.dart';

class Item {
  String reference;

  Item(this.reference);
}

// _MyInherited是一個(gè)InheritedWidget,每次我們通過(guò)點(diǎn)擊“Widget A”按鈕添加一個(gè)Item時(shí)都會(huì)重新創(chuàng)建它
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;// 由于data是永遠(yuǎn)不會(huì)改變的,所以這里需要更新子組件的話直接設(shè)置為true
  }
}

// MyInheritedWidget是一個(gè)Widget,其狀態(tài)包含Items列表??梢酝ㄟ^(guò)“(BuildContext context)的靜態(tài)MyInheritedWidgetState”訪問(wèn)此狀態(tài)。
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, bool rebuild = true]){
    return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
        : context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;// ancestorWidgetOfExactType:返回給定類型的最近祖先小部件,這個(gè)組件創(chuàng)建過(guò)一次就不會(huì)重新創(chuàng)建
  }
}

//MyInheritedWidgetState公開(kāi)一個(gè)getter(itemsCount)和一個(gè)方法(addItem),以便它們可以被小部件使用,這是子小部件樹(shù)的一部分
class MyInheritedWidgetState extends State<MyInheritedWidget>{
  /// List of Items
  List<Item> _items = <Item>[];

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

  // 這個(gè)方法沒(méi)吊用一次都會(huì)重新執(zhí)行build方法,所以_MyInherited也會(huì)重新執(zhí)行一遍
  void addItem(String reference){
    setState((){
      _items.add(new Item(reference));
    });
  }

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

// 每次我們向State添加一個(gè)Item時(shí),MyInheritedWidgetState都會(huì)重建
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(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// WidgetA是一個(gè)簡(jiǎn)單的RaisedButton,當(dāng)按下它時(shí),從最近的MyInheritedWidget調(diào)用addItem方法,它本身是不需要重新更新的,所以我們?cè)O(shè)計(jì)讓他不會(huì)重新更新
class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context,false);
    print("WidgetA重新更新了~~~");
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item');
        },
      ),
    );
  }
}

// WidgetB是一個(gè)簡(jiǎn)單的文本,顯示最接近的MyInheritedWidget級(jí)別的項(xiàng)目數(shù)
class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context,true);//這段代碼其實(shí)就相當(dāng)于注冊(cè)訂閱,true表示需要接受更新
    print("WidgetB重新更新了~~~");
    return new Text('${state.itemsCount}');
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("WidgetC重新更新了~~~");
    return new Text('I am Widget C');
  }
}

上面代碼詳解:說(shuō)白了就是給訂閱的人進(jìn)行Widget的更新
當(dāng)子Widget調(diào)用MyInheritedWidget.of(context)時(shí),它會(huì)調(diào)用MyInheritedWidget的以下方法,并傳遞自己的BuildContext。

 static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
    return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
        : context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;// ancestorWidgetOfExactType:返回給定類型的最近祖先小部件,這個(gè)組件創(chuàng)建過(guò)一次就不會(huì)重新創(chuàng)建
  }

在內(nèi)部,除了簡(jiǎn)單地返回MyInheritedWidgetState的實(shí)例之外,它還將消費(fèi)者窗口小部件訂閱到更改通知。
整個(gè)過(guò)程如下:

  • 調(diào)用MyInheritedWidget.of(context,true)這個(gè)方法本質(zhì)返回的是MyInheritedWidgetState類。它還將消費(fèi)者窗口小部件訂閱到更改通知。這里核心是A與B傳入了context,是通過(guò)AB的context獲取到繼承小部件,而C沒(méi)有傳入。
  • 調(diào)用MyInheritedWidgetState類中的void addItem(String reference)方法,因?yàn)?code>setState()方法,所以會(huì)重新執(zhí)行build方法,最后也就是_MyInherited重新創(chuàng)建,也就是updateShouldNotify執(zhí)行了。其實(shí)就是_MyInherited中的data重新了,里面的數(shù)據(jù)也就是最新的。它檢查是否需要“通知”“使用者”(答案為是)
  • 迭代整個(gè)消費(fèi)者列表(這里是Widget B)并請(qǐng)求他們重建
  • 由于Wiget C不是消費(fèi)者,因此不會(huì)重建。因?yàn)镃沒(méi)有傳入context獲取對(duì)應(yīng)的MyInheritedWidgetState,而且MyInheritedWidgetState也沒(méi)有重新創(chuàng)建_MyInherited,所以就算C在MyInheritedWidget組件的節(jié)點(diǎn)下,也不會(huì)接收到更新的通知。

總的來(lái)說(shuō)就是利用StatefulWidget來(lái)存儲(chǔ)需要改變的數(shù)據(jù),然后StatefulWidget的根節(jié)點(diǎn)設(shè)置為InheritedWidget,然后利用StatefulWidget的state的setState方法重新創(chuàng)建生成新的InheritedWidget節(jié)點(diǎn),最后也就InheritedWidget中定義的新的StatefulWidget變量,然后updateShouldNotify重新調(diào)用通知相關(guān)訂閱的Widget更新。

這里其實(shí)就有點(diǎn)像Android中的eventbus訂閱了就能接收到通知類似。在flutter中類似的框架有Redux,這個(gè)后面再總結(jié)。

?著作權(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)容