前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 ># 使用InheritedWidget传递数据

# 使用InheritedWidget传递数据

作者头像
用户1175783
发布2019-09-23 15:32:41
9050
发布2019-09-23 15:32:41
举报
文章被收录于专栏:用户1175783的专栏

# 使用InheritedWidget传递数据

代码语言:javascript
复制
除了StatefulWidget、StatelessWidget之外flutter还提供了另外一个用的Widget组件即InheritedWidget。那么该组件的作用时什么呢?
# 我们来看一下数据是如何从父widget传递到子widget的

下面我们定义一个嵌套三层的数据传递例子:

代码语言:javascript
复制
class DataTransferAWidget extends StatelessWidget {
  final int data;
  DataTransferAWidget(this.data);
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Expanded(child: Text("A:${data.toString()}")),
          Expanded(
            //子组件依赖自己的data数据
            child: DataTransferBWidget(data),
          )
        ],
      ),
    );
  }
}

class DataTransferBWidget extends StatelessWidget {
  final int data;
  DataTransferBWidget(this.data);
  @override
  Widget build(BuildContext context) {
    return Container(
        alignment: Alignment.center,
        child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              Expanded(child: Text("B:${data.toString()}")),
              //子组件依赖自己的data数据
              Expanded(child: DataTransferCWidget(data))
            ]));
  }
}

class DataTransferCWidget extends StatelessWidget {
  final int data;
  DataTransferCWidget(this.data);
  @override
  Widget build(BuildContext context) {
    return Container(
        alignment: Alignment.center, child: Text("C:${data.toString()}"));
  }
}

class DataTransferDemoPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _DataTransferDemoPageState();
  }
}

class _DataTransferDemoPageState extends State<DataTransferDemoPage> {
  //定义一个待向子widget传递的数据
  int data = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("数据传递"),
      ),
      body: Container(
          alignment: Alignment.center, 
          //向子传递data
          child: DataTransferAWidget(data)),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {
            setState(() {
              data++;
            });
          }),
    );
  }

上面例子你看到,每个DataTransferWidget的构造函数都依赖父widget的data,如果还有第4层,第5层...等嵌套的话,data要不停的通过构造函数传递,甚是麻烦。

既然每层widget依赖的都是根的data,那么为什么不定义一个全局静态变量来做呢?(好想法,我们试一下)

# 使用static代替构造函数传递数据
代码语言:javascript
复制
typedef ChildWidgetBuilder =DataTransferCWidget Function(int);
class DataTransferAWidget extends StatelessWidget {
  //下面代码已经没有意义了
  //final int data;
  //DataTransferAWidget(this.data);
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Expanded(child: Text("A:${DataModel.data.toString()}")),
          Expanded(
            child: DataTransferBWidget(),
          )
        ],
      ),
    );
  }
}

class DataTransferBWidget extends StatelessWidget {
  //下面代码已经没有意义了
  //final int data;
  //DataTransferBWidget(this.data);
  @override
  Widget build(BuildContext context) {
    return Container(
        alignment: Alignment.center,
        child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              Expanded(child: Text("B:${DataModel.data.toString()}")),
              Expanded(child: DataTransferCWidget())
            ]));
  }
}

class DataTransferCWidget extends StatelessWidget {
  //下面代码已经没有意义了
  //final int data;
  //DataTransferCWidget(this.data);
  @override
  Widget build(BuildContext context) {
    return Container(
        alignment: Alignment.center, child: Text("C:${DataModel.data.toString()}"));
  }
}
//静态变量
class DataModel{
  static int data=0;
}
class DataTransferDemoPage extends StatefulWidget {

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

class _DataTransferDemoPageState extends State<DataTransferDemoPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("数据传递"),
      ),
      body: Container(
          alignment: Alignment.center, child: DataTransferAWidget()),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {
            setState(() {
              DataModel.data++;
            });
          }),
    );
  }
}

全局static类型的变量是不是更方便一些呢?!

# 该是InheritedWidget出场的时候了

flutter官方api是这样说的:有效地在树中传播信息的小部件的基类,下面咱们来看一下它的定义:

代码语言:javascript
复制
//我们可以看到该类是一个抽象类
abstract class InheritedWidget {
  //这是一个常量构造函数,常量的作用就是一旦编译后就不可能再被修改
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  //这个函数不在我们的讨论范围之内
  @override
  InheritedElement createElement() => InheritedElement(this);
  //这个函数可以接收旧的对象,用来判断新旧InheritedWidget是否一样,它又什么作用呢?往下看...
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

它是一个抽象类,这就意味着我们要去实现它:

代码语言:javascript
复制
class TestModel {
  int model = 0;
  void add() {
    model++;
  }
}

class MyInheriteWidget extends InheritedWidget {
  //这里我们定义一个我们准备向后代传递的数据
  final DataModel model;
  //另外InheriteWidget需要包裹一个Widget
  //由于InheritedWidget仅用来传输数据,
  //所以这个child或者child的后代需要是一个StatelessWidget或StatefulWidget
  final Widget child;

  MyInheriteWidget({Key key, @required this.model, @required this.child})
      : super(key: key, child: child);

  //这里我们要判断新旧widget是否相同
  @override
  bool updateShouldNotify(MyInheriteWidget oldWidget) {
    return model != oldWidget.model;
  }
  
  static TestModel of(BuildContext context, [bool onChangeBuild = true]) {
    var widget = onChangeBuild
        ? context.inheritFromWidgetOfExactType(MyInheriteWidget)
        : context
            .ancestorInheritedElementForWidgetOfExactType(MyInheriteWidget)
            .widget;
    return (widget as MyInheriteWidget).model;
  }
}

此时,一个可以用与向后台传递数据的InheritedWidget就完成了,下面来看如何使用:

代码语言:javascript
复制
class _InheriteWidgetDemoPage extends State<InheriteWidgetDemoPage> {
  //在一个有状态的widget中定义它需要维护的状态
  var testModel = TestModel();

  @override
  Widget build(BuildContext context) {
    //使用InheritedWidget向后代传递数据
    return MyInheriteWidget(
      model: testModel,
      child: Scaffold(
          appBar: AppBar(title: Text("title")),
          //或许你想这样使用,这是错误的,我们应该获取的是InheritedWidget的数据而不是当前widget的
          //body:Text(testModel.model.toString())
          //为了更清楚,我们将获取InheritedWidget的数据放在一个单独的widget中执行,如下
          body: TestAWidget(),
          floatingActionButton: Builder(
            builder: (context) => FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                setState(() {
                  MyInheriteWidget.of(context).add();
                });
              },
            ),
          )),
    );
  }
}

class TestAWidget extends StatelessWidget {
  TestAWidget();
  @override
  Widget build(BuildContext context) {
   	//官方文档提到我们应该这样获取InheritedWidget上传递的数据,是不是有点类似全局静态变量呢?!
    //不同的是 这是从context获取的,因为context可以贯穿整个widget依赖树,像android的context
    //官方给的demo不是下面这样
    //var myInheritedWidget = context.inheritFromWidgetOfExactType(MyInheriteWidget) as MyInheriteWidget;
    //return Center(child: Text(myInheritedWidget.model.model.toString()));
    //应该是这样,将获取传递对象的代码放在MyInheritedWidget中,这仅是为了代码可读性
   var model = MyInheriteWidget.of(context).model.toString();
    return Center(child: Text(model));
  }
}

此时InheritedWidget向后代传递数据的方式已经完了,从现有的实现来说并没有比全局静态变量有什么优略之处,如果仅是这样也就没有它存在的价值了,请往下看...

# updateShouldNotify的作用

要弄清楚updateShouldNotify的作用我们还要翻一翻源码了,不然只看api文档还是一头雾水,上源码:

代码语言:javascript
复制
//我们知道一个widget由:widget,element,ReanderObject三部分组成
class InheritedElement extends ProxyElement {
    
    ...
        
    @override
    void updated(InheritedWidget oldWidget) {
        //通过字符串搜索你会找到该方法是在此处调用的
        if (widget.updateShouldNotify(oldWidget))
            super.updated(oldWidget);
    }
    
    @override
    void notifyClients(InheritedWidget oldWidget) {
      		...
            notifyDependent(oldWidget, dependent);
        }
    }

    @protected
    void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
        dependent.didChangeDependencies();
    }
    ...
}

class StatefulElement extends ComponentElement {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //调了这么一圈最后是为了调用didChangeDependencies方法
    _state.didChangeDependencies();
  }
}

abstract class ProxyElement extends ComponentElement{
    @protected
    void updated(covariant ProxyWidget oldWidget) {
        notifyClients(oldWidget);
    }
}

从上面重要的源码可以看到最后updateShouldNotify决定了didChangeDependencie()会不会被调用。

另外,分析源码的调用过程,也可以借助idea的debugger模式,在Frames里面我们可以看到代码是如何一步步被调用的。

# didChangeDependencie又是什么

didChangeDependencie是State中定义的一个回调函数,而State正是暴漏StatefulWidget生命周期的地方,我们可以同步实现State的不同方法,来对widget的生命周期变化时做出一些响应。

上面例子我们定义了一个无状态的TestAWidget来演示如果获取InheritedWidget要向子传递的数据,下面我们通过一个有状态的控件来展示在获取数据的同时响应didChangeDependencie的调用。

代码语言:javascript
复制
lass TestBWidget extends StatefulWidget {
    
  @override
  State<StatefulWidget> createState() {
    return _TestBWidgetState();
  }
}

class _TestBWidgetState extends State<TestBWidget> {
  //这里就是要被调用的地方
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('TestBWidget');
  }

  @override
  Widget build(BuildContext context) {
    var model = MyInheriteWidget.of(context).model.toString();
    return Center(child: Text(model));
  }
}

现在我们已经知道了updateShouldNotify返回true时didChangeDependencies()就会被调用。

InheritedWidget是为了向后代传递数据,如果InheritedWidget发生了嵌套呢?可能你只想响应某一个先辈的didChangeDependencies()调用,这也是可以,通过ancestorInheritedElementForWidgetOfExactType来获取先辈的数据,不会将自己的didChangeDependencies注册进该先辈的调用集合。

# 有选择性的注册didChangeDependencies调用

前面的代码中有一段是这样的:

代码语言:javascript
复制
  static TestModel of(BuildContext context, [bool onChangeBuild = true]) {
    //通过一个变量来控制调用哪个方法获取先辈的数据
    var widget = onChangeBuild
        ? context.inheritFromWidgetOfExactType(MyInheriteWidget)
        : context
            .ancestorInheritedElementForWidgetOfExactType(MyInheriteWidget)
            .widget;
    return (widget as MyInheriteWidget).model;
  }

要搞清楚inheritFromWidgetOfExactType与ancestorInheritedElementForWidgetOfExactType的区别,我认为看源代码是最直接的:

代码语言:javascript
复制
...  
@override
  InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
   
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    //重点再这里
    if (ancestor != null) {
      return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

  @override
  InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
    
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    return ancestor;
  }

  @override
  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    _dependencies ??= HashSet<InheritedElement>();
      
    //将widget添加到依赖集合后就会收到didChangeDependencies调用
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
...

下面配上一张流程图来加深一下印象:

# 总结

  1. InheritedWidget用与向后代传递/共享数据
  2. 通过updateShouldNotify方法可以控制是否要调用后代的didChangeDependencies方法
  3. didChangeDependencies被调用的前提是这个后代是一个StatefulWidget,且是通过inheritFromWidgetOfExactType方法来获取先辈的数据。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # 使用InheritedWidget传递数据
    • # 该是InheritedWidget出场的时候了
      • # updateShouldNotify的作用
      • # didChangeDependencie又是什么
      • # 有选择性的注册didChangeDependencies调用
    • # 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档