本文仅作个人学习InheritedWidget记录。

感谢文章 widget-state-context-inheritedwidget

在 Flutter 中 InheritedWidget 用于在 Widget 树中向子Widget传播、共享信息。

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

   final String data;

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

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

在这个名为 MyInheritedWidget 中,字段 data 被暴露出来供外界使用,of 方法用于获取 MyIheritedWidget 或对其注册监听。

为了能够传播/共享某些数据,需要将 InheritedWidget 放置在 widget 树的顶部,所以说 @required Widget child  是必要的。

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

那么子节点如何获取 InheritedWidget 中的数据?

通常 InheritedWidget 会带有一个静态的 of 方法,如同 Theme.of(context) ,所以我们只需要这样:

class MyChildWidget... {
   ...

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

      ///
      /// 此刻,该 widget 可使用 MyInheritedWidget 暴露的数据
      /// 通过调用:inheritedWidget.data
      ///
      return Container(
         color: inheritedWidget.data.color,
      );
   }
}

说到这里我们可以看一下 InheritedWidget 使用场景:

Example

WidgetA 是一个按钮,在点击后会对 WidgetB 进行数据更新。

WidgetB 是一个显示数据的 Text。

WidgetC 是一个内容不变的 Text。

WidgetD 是一个按钮,点击后能获取数据。

我们希望点击 WidgetA 时,对 WidgetB 进行更新,但是我们不允许 Test2Widget 、WidgetA、WidgetC、WidgetD 重绘。

代码:

import 'package:flutter/material.dart';

class Item {
  final String name;

  const Item(this.name);

  @override
  String toString() {
    return 'Item{name: $name}';
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Item && runtimeType == other.runtimeType && name == other.name;

  @override
  int get hashCode => name.hashCode;
}

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

  final Item data;

  @override
  bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
    print('old: ${oldWidget.data}');
    print('new: $data');
    print(data != oldWidget.data);
    return data != oldWidget.data;
  }

  static MyInheritedWidget of(BuildContext context, {bool rebuild = true}) {
    if (!rebuild) return context.findAncestorWidgetOfExactType<MyInheritedWidget>();
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }
}

class TopWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Test1Widget(
      child: Scaffold(
        body: Center(
          child: Column(
            children: [
              Test2Widget(),
              WidgetA(),
            ],
            mainAxisSize: MainAxisSize.min,
          ),
        ),
      ),
    );
  }
}

class Test1Widget extends StatefulWidget {
  final Widget child;

  const Test1Widget({Key key, this.child}) : super(key: key);

  @override
  _Test1WidgetState createState() => _Test1WidgetState();
}

class _Test1WidgetState extends State<Test1Widget> {
  var item = const Item('AB');

  void update() {
    item = Item('${DateTime.now().millisecondsSinceEpoch}');
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    // print('Test1Widget build');
    return MyInheritedWidget(
      data: item,
      child: widget.child,
    );
  }
}

class Test2Widget extends StatefulWidget {
  @override
  _Test2WidgetState createState() => _Test2WidgetState();
}

class _Test2WidgetState extends State<Test2Widget> {
  @override
  Widget build(BuildContext context) {
    print('Test2Widget build');
    return Row(
      children: [
        WidgetB(),
        WidgetC(),
        WidgetD()
      ],
    );
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('-------WidgetA Build------');
    return RaisedButton(
      onPressed: () {
        context.findAncestorStateOfType<_Test1WidgetState>().update();
      },
      child: Text('ADD'),
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final data = MyInheritedWidget.of(context).data;
    print('-------WidgetB Build------');
    return Text('data: $data');
  }
}

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

class WidgetD extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('-------WidgetD Build------');
    return FlatButton(
      child: Text('PRINT'),
      onPressed: () {
        print(MyInheritedWidget.of(context, rebuild: false).data);
      },
    );
  }
}

Item 是一个数据类。

1. MyInheritedWidget 继承 InheritedWidget,用于保存数据。可以通过 MyInheritedWidget.of 方法获取 MyInheritedWidget 实例。

2. _Test1WidgetState 内存放着原始的数据 item,同时 build 返回了 MyInheritedWidget。 当点击 WidgetA 时,findAncestorStateOfType 会向上寻找 _Test1WidgetState,然后调用其 update 方法:

context.findAncestorStateOfType<_Test1WidgetState>().update();

_Test1WidgetState 中的 update 方法:

  void update() {
    item = Item('${DateTime.now().millisecondsSinceEpoch}');
    setState(() {});
  }

此方法更新了原始数据 item,同时 setState 重构页面,因为 _Test1WidgetState build 返回了 MyInheritedWidget,所以当调用 update 后,MyInheritedWidget 也会被重新创建,从而调用 MyInheritedWidget 中的 updateShouldNotify 方法。

updateShouldNotify 方法用来告诉 InheritedWidget 如果对数据进行了修改,是否必须将通知传递给所有已注册的子 widget(何为已注册的子 Widget?继续往下看)。

所谓的已注册的子Widget 即为调用了 MyInheritedWidget.of 方法,同时传参 rebuild 为 ture的 Widget

在 of 方法中:

  static MyInheritedWidget of(BuildContext context, {bool rebuild = true}) {
    if (!rebuild) return context.findAncestorWidgetOfExactType<MyInheritedWidget>();
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }

context.dependOnInheritedWidgetOfExactType<T>() 用于在 Widget 树中向上寻找类型为 T 的 InheritedWidget,同时以调用的 context 来注册(context 与 state 是绑定的)。findAncestorWidgetOfExactType 为仅找到 InheritedWidget 而不注册。

当 updateShouldNotify 返回 true 时,会对已注册的子Widget 进行重建。我们在 updateShouldNotify 中判断了新旧数据的一致性,当数据不同时就返回 true。

在我们点击 WidgetA,调用了 update 方法,更新了数据 (Item),造成 MyInheritedWidget 重建,同时由于新旧数据不同(Item)所以通过 updateShouldNotify 去发送重建的信号,当已注册的子 Widget 接收到信号就会重建。

WidgetB 是一个已注册的子 Widget,获取了 MyInherited 中的 data,同时需要对其进行注册,如果 data 发生改变,那么该 Widget 就需要更新(rebuild)

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final data = MyInheritedWidget.of(context).data;
    print('-------WidgetB Build------');
    return Text('data: $data');
  }
}

WidgetD 中仅获取了 MyInheritedWidget 而不对其监听。

class WidgetD extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('-------WidgetD Build------');
    return FlatButton(
      child: Text('PRINT'),
      onPressed: () {
        print(MyInheritedWidget.of(context, rebuild: false).data);
      },
    );
  }
}

感谢:

https://juejin.cn/post/6844903784187953165#heading-31

https://blog.csdn.net/vitaviva/article/details/105462686

https://www.bilibili.com/video/av87592053/


Android 开发者、影视后期内容(包装)制作者、Unity 2D游戏开发者