Flutter : 关于 InheritedWidget

写在前面

InheritedWidget是一个用于数据共享的组件,它的数据传输方向是从上到下,所以当我们把数据放在它里面,当有数据变更的时候,依赖它的那些子 Widget 都可以获得数据的变更。

内容

abstract class InheritedWidget extends ProxyWidget {

  const InheritedWidget({ Key? key, required Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

updateShouldNotify()方法用于让 Framework 判断是否去提醒那些有依赖于这个InheritedWidget的 Widget。

InheritedWidget是一个抽象类,意味着我们必须去实现它,这里以《Flutter 实战》一书里的例子 7.2 数据共享(InheritedWidget) 作为表示:

class ShareDataWidget extends InheritedWidget {
  ShareDataWidget({
    @required this.data,
    Widget child
  }) :super(child: child);
    
  final int data; //需要在子树中共享的数据,保存点击次数
    
  //定义一个便捷方法,方便子树中的widget获取共享数据  
  static ShareDataWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

  //该回调决定当data发生变化时,是否通知子树中依赖data的Widget  
  @override
  bool updateShouldNotify(ShareDataWidget old) {
    //如果返回true,则子树中依赖(build函数中有调用)本widget
    //的子widget的`state.didChangeDependencies`会被调用
    return old.data != data;
  }
}

class _TestWidget extends StatefulWidget {
  @override
  __TestWidgetState createState() => new __TestWidgetState();
}

class __TestWidgetState extends State<_TestWidget> {
  @override
  Widget build(BuildContext context) {
    //使用InheritedWidget中的共享数据
    return Text(ShareDataWidget
        .of(context)
        .data
        .toString());
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
    //如果build中没有依赖InheritedWidget,则此回调不会被调用。
    print("Dependencies change");
  }
}

class InheritedWidgetTestRoute extends StatefulWidget {
  @override
  _InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState();
}

class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return  Center(
      child: ShareDataWidget( //使用ShareDataWidget
        data: count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(bottom: 20.0),
              child: _TestWidget(),//子widget中依赖ShareDataWidget
            ),
            RaisedButton(
              child: Text("Increment"),
              //每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新  
              onPressed: () => setState(() => ++count),
            )
          ],
        ),
      ),
    );
  }
}

当我们点击按钮,然后调用setState()的方法,在下一帧来临的时候,就会调用InheritedWidgetTestRoute进行 rebuild,然后去递归调用其 child 的update()方法。

由于继承关系:

InheritedElement
ProxyElement
ComponentElement
Element

会先调用ProxyElementupdate()方法,里面调用了updated()方法:

abstract class ProxyElement extends ComponentElement {
  @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    assert(widget != null);
    assert(widget != newWidget);
    super.update(newWidget);
    assert(widget == newWidget);
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }
  
  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }
}

InheritedElement重写了updated()方法,在这里就用到了我们的updateShouldNotify()方法,整个流程下来可以知道就是遍历一个Map类型的_dependents,把里面的依赖的Element调用它们的didChangeDependencies方法。

class InheritedElement extends ProxyElement {
  final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
  ...
  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }
  
  @override
  void notifyClients(InheritedWidget oldWidget) {
    ...
    for (final Element dependent in _dependents.keys) {
      ...
      notifyDependent(oldWidget, dependent);
    }
  }
  ...
 @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }
}

那么是在哪里添加这些依赖者的呢?

abstract class Element extends DiagnosticableTree implements BuildContext {

...
  @override
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
    ...
    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
    if (ancestor != null) {
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
    ...
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies!.add(ancestor);
    // 这里的 this 就是当前 Element 实例
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }

}

我们在Element类里可以看到,在我们前面调用的dependOnInheritedWidgetOfExactType()方法里,会调用到dependOnInheritedElement()方法,里面会调用到我们当前ShareDataWidget所对应的InheritedElementupdateDependencies()方法。

class InheritedElement extends ProxyElement {
...
  @protected
  void updateDependencies(Element dependent, Object? aspect) {
    setDependencies(dependent, null);
  }
  @protected
  void setDependencies(Element dependent, Object? value) {
    _dependents[dependent] = value;
  }
}

也就是说当我们某个 Widget 通过调用ShareDataWidgetof静态方法的时候,就把自己给注册进去了。这样后续的更新就可以通知到了。

那么使用dependOnInheritedWidgetOfExactType()方法我们可以看到由于这样会注册依赖关系,所以当数据有更新的时候,这些依赖关系的子 Widget 都会被调用到didChangeDependencies()方法,然后造成它们都需要进行build()

如果我们只想要ShareDataWidget的数据,而不想要didChangeDependencies()被调用,那么就要用另外一个方法getElementForInheritedWidgetOfExactType<T extends InheritedWidget>(),把ShareDataWidgetof方法改写如下:

static ShareDataWidget of(BuildContext context) {
  //return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  return context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget;
}
abstract class Element extends DiagnosticableTree implements BuildContext {
 ...
  @override
  InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
    ...
    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
    return ancestor;
  }
  ...
}

getElementForInheritedWidgetOfExactType<T extends InheritedWidget>()方法跟dependOnInheritedWidgetOfExactType()的区别就是少了那一层注册关系。

总结

  1. 实现InheritedWidget,在updateShouldNotify()方法里写通过什么条件会触发提醒
  2. 通过context.dependOnInheritedWidgetOfExactType()方法,既让子 Child 可以访问到数据,同时也起到了把子 Child 自己的 Element 注册到InheritedWidget里。
  3. 当数据变更,触发到InheritedWidget实现类的update()方法的时候,它会遍历之前注册进来的 Element 列表,然后调用它们的didChangeDependencies()方法,接着 Framework 会调用build方法。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值