美文网首页
flutter中InheritedWidget的介绍和运用

flutter中InheritedWidget的介绍和运用

作者: 萤火虫离别的礼物 | 来源:发表于2019-08-07 18:43 被阅读0次

    InheritedWidget 不继承自StatefulWidget,而是 InheritedWidget -> ProxyWidget -> Widget 这样的继承关系。简单来说,InheritedWidget 的作用是向它的子 Widget 有效地传播和分享数据,当 InheritedWidget 作为一个Parent Widget时,它下面的Widget tree的所有Widget都可以去和 InheritedWidget 发生数据传递和交互。当数据发生改变时,一部分控件需要 rebuild,另外的控件不需要 rebuild 的时候,可以使用 InheritedWidget,具体的介绍结合代码来看。
    下面是 InheritedWidget 的一个实例:

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

    updateShouldNotify 是必须重写的一个方法,这个方法来决定什么时候需要去rebuild 控件
    of 是一个类方法,返回了它自己,inheritFromWidgetOfExactType这个方法,我理解的是从父Widget中根据类型去找响应的Widget,但这个方法还有其他的作用,会在后面详细介绍。
    初始化方法中,会传一个Child,并传递给super,也就是它的子Widget。

    使用这个类:

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

    子Widget怎么获取数据?

    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 A是一个按钮,点击的时候购物车数量加1.
    • Widget B是一个显示购物车数量的文本.
    • Widget C是显示一个固定的文本
    • 在点击Widget A的时候,Widget B刷新数据,而不需要rebuild Widget C

    代码如下:

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

    解释说明:

    • 当点击Widget A的时候,_MyInherited 会被重新创建一个
    • MyInheritedWidget 相当于是一个购物车,它的State通过static MyInheritedWidgetState of(BuildContext context)可以获取到.
    • MyInheritedWidgetState 提供了公开的方法,getter (itemsCount)和addItem,这两个方法可以被子Widget调用.
    • 每次我们添加一项到购物车时,MyInheritedWidgetState会rebuild

    InheritedWidget是怎么去通知子Widget刷新数据的呢?

    当一个子Widget调用MyInheritedWidget.of(context)时,会走下面的代码,把它自己的context传进来

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

    这个方法其实是做了两件事:

    • 获取MyInheritedWidgetState里面的数据
    • 会将调用该方法的Widget加入订阅者行列,当数据发生改变的时候,会通知这些Widget刷新数据,也就是rebuild.

    工作的原理总结起来如下:
    因为Widget A和Widget B订阅了InheritedWidget,所以点击Widget A的时候会发生:

    • 触发了MyInheritedWidgetState的addItem的方法
    • MyInheritedWidgetState的addItem的方法添加了新的一项数据到data中
    • 触发了setState(),MyInheritedWidgetState rebuild
    • 重新创建了一个 _MyInherited 对象,传入了新的数据,记录了新的State
    • _MyInherited去查看是否需要通知订阅者,因为返回的是true,所以会通知订阅者
    • Widget A和Widget B 被 rebuild了,Widget C没有被订阅,所以不会被rebuild

    因为事实上Widget A是不需要被通知的,所以为了解决这个问题,需要修改代码

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

    ancestorWidgetOfExactType 方法只会去获取Widget,不会发生订阅,所以Widget A就不会订阅了。

    参考链接: https://www.didierboelens.com/2018/06/widget---state---context---inheritedwidget/

    相关文章

      网友评论

          本文标题:flutter中InheritedWidget的介绍和运用

          本文链接:https://www.haomeiwen.com/subject/jbyadctx.html