Flutter中InheritedWidget的使用

2022-05-11  本文已影响0人  码农的地中海

定义:

在Tree中从上往下高效传递数据的基类widget , 定义为:abstract class InheritedWidget extends ProxyWidget

InheritedWidget是什么?

原理:

Flutter的响应式开发与React类似,数据都是自顶向下的。

假设有祖先组点A,中间经过结点B, C,然后到结点D,D需要从A中获取数据f,那按照自顶向下数据流转,f需要依次传递给B及C,最后才到C。这样开发极为不灵活,成本也比较高。所有Flutter需要有跨结点(只能是祖先后代节点,不能跨兄弟节点)高效传递数据的方案。

源码定义:

/// Base class for widgets that efficiently propagate 
///information down the tree.

/// To obtain the nearest instance of a particular type of inherited
 ///widget from a build context, use 
///[BuildContext.inheritFromWidgetOfExactType].

/// Inherited widgets, when referenced in this way, will cause the 
///consumer to rebuild when the inherited widget itself changes state.

大体意思如下:

InheritedWidget 是在树中高效向下传递信息的基类部件;

调用[BuildContext.inheritFromWidgetOfExactType]方法可以从 BuildContext 中获取到最近的 InheritedWidget 类型的实例;

在 InheritedWidget 类型的控件被引用,也就是调用过 inheritFromWidgetOfExactType 方法后,当 InheritedWidget 自身状态改变时,会导致引用了 InheritedWidget 类型的子控件重构(rebuild)。

InheritedWidget 用法案例

定义数据模型

这里随便定义一个 Person 类。

//数据模型
class Person {
  String name;

  int age;

  Person(this.name, this.age);

  @override
  String toString() {
    // TODO: implement toString
    return "我叫${this.name},今年${this.age}了";
  }

}

自定义 InheritedWidget 控件类

创建一个类继承 InheritedWidget,并实现 updateShouldNotify 方法。

class InheriedDataWidget extends InheritedWidget {
  final Person person; //需要在树中共享的数据

  InheriedDataWidget({@required this.person,Widget child})
      : super(child: child);

  //定义一个便捷方法,方便子树中的widget获取共享数据
  static InheriedDataWidget of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(InheriedDataWidget);
  }

  @override
  bool updateShouldNotify(InheriedDataWidget oldWidget) {
    // TODO: implement updateShouldNotify
    //如果返回true,则子树中依赖(build函数中有调用)本widget的子 . 
    //widget的`state.didChangeDependencies`方法会被调用
    return this.person.name != oldWidget.person.name ||
        this.person.age != oldWidget.person.age;
  }
}

之前说到调用[BuildContext.inheritFromWidgetOfExactType]方法可以从 BuildContext 中获取到最近的 InheritedWidget 类型的实例,所以此处定义一个静态的 of 方法,通过传入的 context 获取到最近的 InheriedDataWidget 实例。

InheritedWidget使用:

1.定义数据模型

这里随便定义一个 Person 类。

//数据模型
class Person {
  String name;

  int age;

  Person(this.name, this.age);

  @override
  String toString() {
    // TODO: implement toString
    return "我叫${this.name},今年${this.age}了";
  }

2.自定义 InheritedWidget 控件类

创建一个类继承 InheritedWidget,并实现 updateShouldNotify 方法。

class InheriedDataWidget extends InheritedWidget {
  final Person person; //需要在树中共享的数据

  InheriedDataWidget({@required this.person,Widget child})
      : super(child: child);

  //定义一个便捷方法,方便子树中的widget获取共享数据
  static InheriedDataWidget of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(InheriedDataWidget);
  }

  @override
  bool updateShouldNotify(InheriedDataWidget oldWidget) {
    // TODO: implement updateShouldNotify
    //如果返回true,则子树中依赖(build函数中有调用)本widget的子 . 
    //widget的`state.didChangeDependencies`方法会被调用
    return this.person.name != oldWidget.person.name ||
        this.person.age != oldWidget.person.age;
  }
}

之前说到调用[BuildContext.inheritFromWidgetOfExactType]方法可以从 BuildContext 中获取到最近的 InheritedWidget 类型的实例,所以此处定义一个静态的 of 方法,通过传入的 context 获取到最近的 InheriedDataWidget 实例。

3.InheriedDataWidget 的使用

InheriedDataWidget 使用起来也很简单,它本身也是一个控件,只要在任意一个页面的子控件调用其构造方法就行,这里我们定义一个形如的 Widget 树。

WidgetA 类
//WidgetA
class WidgetA extends StatefulWidget {
  @override
  _WidgetAState createState() => _WidgetAState();
}

class _WidgetAState extends State<WidgetA> {
  Person person = new Person("小明", 24);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        //调用构造,在此节点共享了一个Person类型的数据
        child: InheriedDataWidget(
          person: person,
          child: WidgetA1(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            //在setState中刷新Person的值
            this.person=new Person(person.name, person.age+1);
          });
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

WidgetA 是一个 StatefulWidget 类型的控件,可以调用 setState 刷新,如果是继承 Stateless 类型的控件,那我们也可以通过 Stream 或者其他方式刷新数据,感兴趣请看[什么是 Stream? Dart

WidgetA1 类
//控件WidgetA
class WidgetA1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey,
      width: MediaQuery.of(context).size.width,
      margin: EdgeInsets.all(10),
      child: Column(
        children: <Widget>[
          Text(
            "A1 Widget",
            style: TextStyle(color: Colors.white,fontSize: 20),
          ),
          //调用InheriedDataWidget.of()获取数据,其实也就是调用了 . 
         //inheritFromWidgetOfExactType方法
          Text(
            "共享数据的信息: ${InheriedDataWidget.of(context).person.toString()}",
            style: TextStyle(color: Colors.white,fontSize: 20),
          ),
          Container(
            margin: EdgeInsets.only(top: 40),
            color: Colors.deepOrangeAccent,
            height: MediaQuery.of(context).size.height*0.5,
            width: MediaQuery.of(context).size.width,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                WidgetA1_1(),
                WidgetA1_2(),
                WidgetA1_3(),
              ],
            ),

WidgetA1_1 类

class WidgetA1_1 extends StatefulWidget {
  @override
  _WidgetA1_1State createState() => _WidgetA1_1State();
}

class _WidgetA1_1State extends State<WidgetA1_1> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      height: 140,
      width: MediaQuery.of(context).size.width/3-20,
      //引用共享数据
      child: Text("WidgetA1_1ful\n\n共享数据是:${InheriedDataWidget.of(context).person.toString()}"),
    );
  }


  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    //当WidgetA中Person数据发生改变时,此方法会被调用,
  //前提是updateShouldNotify返回true
    print("WidgetA1_1ful===>didChangeDependencies");
  }
}

WidgetA1_2 类

class WidgetA1_2 extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      height: 140,
      width: MediaQuery.of(context).size.width/3-20,
      child: Text("WidgetA1_2less\n\n共享数据是:${InheriedDataWidget.of(context).person.toString()}"),
    );;
  }

}

WidgetA1_3 类

class WidgetA1_3 extends StatefulWidget {
  @override
  _WidgetA1_3State createState() => _WidgetA1_3State();
}

class _WidgetA1_3State extends State<WidgetA1_3> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      height: 140,
      width: MediaQuery.of(context).size.width/3-20,
      child: Text("WidgetA1_3ful\n\n未引用共享数据信息"),
    );
  }


  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    print("WidgetA1_3===>didChangeDependencies");
  }
}

运行结果

当我们点击 floatingActionButton 的时候,WidgetA1, WidgetA1_1, WidgetA1_2 的控件都会更新 Person 的信息,而且每点 floatingActionButton 一次,
当我们点击 floatingActionButton 的时候,WidgetA1, WidgetA1_1, WidgetA1_2 的控件都会更新 Person 的信息,而且每点 floatingActionButton 一次,都会输出:

WidgetA1_1ful===>didChangeDependencies

如果我们试图在和 WidgetA 的同一层级的兄弟节点去访问 InheriedDataWidget 的 Person 数据,是不行的,因为父节点中并没有插入 InheriedDataWidget。

把 WidgetB 和 WidgetA 保持同一节点

//WidgetB
class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //此处将报错,因为WidgetB 与WidgetA 是兄弟节点,
   //B中并未找到A中 InheriedDataWidget 的数据
    return Text(InheriedDataWidget.of(context).person.toString());
  }
}

这也体现了 Inheried(遗传) 这一单词的特性,遗传只存在于父子。兄弟不存在遗传的关系。

这种数据共享的方式在某些场景还是很有用的,就比如说全局主题,字体大小,字体颜色的变更,只要在 App 根层级共享出这些配置数据,然后在触发数据改变之后,所有引用到这些共享数据的地方都会刷新,这换主题,字体是不是就很轻松,事实上 Theme.of(context).primaryColor 之流就是这么干的。

总结:

以上就是有关InheritedWidget的使用。自己也是从事Android开发5年有余了;整理了一些Android开发技术进阶资料;https://docs.qq.com/doc/DUkNRVFFzTG96VHNiAndroid技术进阶手册丶面试题纲丶核心笔记。

上一篇 下一篇

猜你喜欢

热点阅读