百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Flutter——InhritedWidget

bigegpt 2024-08-12 14:31 3 浏览

在介绍Flutter provider这一篇的时候,我们提到了InhritedWidget,并介绍了Provider插件的底层就是通过封装InhritedWidget实现的。InhritedWidget提供了一种在 Widget 树中从上到下共享数据的方式。还有一些其他的框架,比如 BlocRedux等都是使用了 InheritedWidget。

简介

InheritedWidget 组件是功能型组件,提供了沿树向下,共享数据的功能,即子组件可以获取父组件(InheritedWidget 的子类)的数据,通过BuildContext.dependOnInheritedWidgetOfExactType 获取。InheritedWidget 组件的共享数据是沿着树从上到下。

试想一下下面的这个场景,有一颗组件树,A、F 组件依赖同一数据,如下:A、F 组件要如何获取到数据呢?

有一种实现方式是 通过构造函数透传,数据通过A传递给B,B传递给C、E,C和E在传递给F,如下图虚线的传递:

return A(
  data:data
  child:B(
    data:data
    child:C(
      data:data
      child:F(
        data:data
      )   
    )
  )
);

这样的实现缺点非常明显,B、C组件不需要 data 数据,如果组件树比较深的话,那将是噩梦。为了处理此问题,Flutter Framework 提供了 InheritedWidget 组件。

简单使用

InheritedWidget Widget 允许它的子 Widget 访问父 Widget 中的数据,使用它可以省去在 Widget 之间传递的数据的麻烦,任意的子 Widget 都可以共享这些数据。

  1. 首先编写一个继承自 InheritedWidget 的类,代码如下
class InheritedInfoWidget extends InheritedWidget {
  InheritedInfoWidget({
    required this.number,
    required this.child,
    Key? key,
  }) : super(
          key: key,
          child: child,
        );

  final int number;
  final Widget child;

  @override
  bool updateShouldNotify(covariant InheritedInfoWidget oldWidget) {
    return oldWidget.number != number;
  }

  static InheritedInfoWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<InheritedInfoWidget>();
  }
}
  1. 然后再编写一个 InfoChildWidget 来展示数字,代码如下:
class InfoChildWidget extends StatefulWidget {
  const InfoChildWidget({Key? key}) : super(key: key);

  @override
  State<InfoChildWidget> createState() => _InfoChildWidgetState();
}

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

  @override
  Widget build(BuildContext context) {
    final int number = InheritedInfoWidget.of(context)!.number;
    return Text('$number');
  }
}
  1. 增加一个界面,用于包含以上组件
class InheritedDemoPage extends StatefulWidget {
  const InheritedDemoPage({Key? key}) : super(key: key);

  @override
  State<InheritedDemoPage> createState() => _InheritedDemoPageState();
}

class _InheritedDemoPageState extends State<InheritedDemoPage> {
  int _number = 0;

  void _incrementCounter() {
    setState(() {
      _number++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('inherited page demo'),
        centerTitle: true,
      ),
      body: InheritedInfoWidget(
        number: _number,
        child: Center(
          child: InfoChildWidget(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: Icon(
          Icons.add,
        ),
      ),
    );
  }
}

从上面的代码我们可以看出,InheritedDemoPage State 中 的 _number 变量值在 InfoChildWidget 中通过 InheritedInfoWidget.of(context)!.number 可以直接使用,而无需通过后传递参数。

此时每当我当我们点击一下底部 ?,didChangeDependencies 便会调用,当 State 对象的依赖发生变化时会进行调用,而这个“依赖”指的就是子 widget 是否使用了父 widget 中 InheritedWidget 的数据。如果使用了,则代表子 Widget 有依赖;如果没有使用则代表没有依赖。这种机制可以使子组件在所依赖的 InheritedWidget 变化时来更新自身,例如:例如系统语言 Locale 或者应用主题等。

如果我们调整InfoChildWidget代码如下:

class InfoChildWidget extends StatelessWidget {
  const InfoChildWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text('number');
  }
}

我们会发现点击+的时候,InfoChildWidget的build不会被调用。

深入

首先看一下 InheritedWidget 的定义:

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => new InheritedElement(this);
  
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

它是一个继承自 ProxyWidget 的抽象类。内部没什么逻辑,除了实现了一个 createElement 方法之外,还定义了一个 updateShouldNotify() 接口。 每次当 InheritedElement 的实例更新时会执行该方法并传入更新之前对应的 Widget 对象,如果该方法返回 true 那么依赖该 Widget 的(在 build 阶段通过 inheritFromWidgetOfExactType 方法查找过该 Widget 的子 widget)实例会被通知进行更新;如果返回 false 则不会通知依赖项更新。这个机制和 React 框架中的 shouldComponentUpdate 机制很像。

每个 Element 实例上都有一个 _inheritedWidgets 属性。该属性的类型为:

HashMap<Type, InheritedElement>

其中保存了祖先节点中出现的 InheritedWidget 与其对应 element 的映射关系。在 element 的 mount 阶段active 阶段,会执行 _updateInheritance() 方法更新这个映射关系。对于普通 Element 实例,_updateInheritance() 只是单纯把父 element 的 _inheritedWidgets 属性保存在自身 _inheritedWidgets。从而实现映射关系的层层向下传递。

void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }

由 InheritedWidget 创建的 InheritedElement 重写了该方法

 void _updateInheritance() {
    assert(_active);
    final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
    if (incomingWidgets != null)
      _inheritedWidgets = new HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = new HashMap<Type, InheritedElement>();
    _inheritedWidgets[widget.runtimeType] = this;
  }

可以看出 InheritedElement 实例会把自身的信息添加到 _inheritedWidgets 属性中,这样其子孙 element 就可以通过前面提到的 _inheritedWidgets 的传递机制获取到此 InheritedElement 的引用。

数据更新

首先看一下 inheritFromWidgetOfExactType 的实现:

  InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      _dependencies ??= new HashSet<InheritedElement>();
      _dependencies.add(ancestor);
      ancestor._dependents.add(this);
      return ancestor.widget;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

首先在 _inheritedWidget 映射中查找是否有特定类型 InheritedWidget 的实例。如果有则将该实例添加到自身的依赖列表中,同时将自身添加到对应的依赖项列表中。这样该 InheritedWidget 在更新后就可以通过其 _dependents 属性知道需要通知哪些依赖了它的 widget。

每当 InheritedElement 实例更新时,会执行实例上的 notifyClients 方法通知依赖了它的子 element 同步更新。notifyClients 实现如下:

  void notifyClients(InheritedWidget oldWidget) {
    if (!widget.updateShouldNotify(oldWidget))
      return;
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (Element dependent in _dependents) {
      assert(() {
        // check that it really is our descendant
        Element ancestor = dependent._parent;
        while (ancestor != this && ancestor != null)
          ancestor = ancestor._parent;
        return ancestor == this;
      }());
      // check that it really depends on us
      assert(dependent._dependencies.contains(this));
      dependent.didChangeDependencies();
    }
  }

首先执行相应 InheritedWidget 上的 updateShouldNotify 方法判断是否需要通知,如果该方法返回 true 则遍历 _dependents 列表中的 element 并执行他们的 didChangeDependencies() 方法。这样 InheritedWidget 中的更新就通知到依赖它的子 widget 中了。

小结

本文简单的从示例演示了如何使用InheritedWidget,以及InheritedWidget能帮我们解决哪些问题,然后从源码的角度讨论了 InheritedWidget 的实现原理。总之,InheritedWidget在Flutter中是一个非常非常重要的组件。

相关推荐

了解Linux目录,那你就了解了一半的Linux系统

大到公司或者社群再小到个人要利用Linux来开发产品的人实在是多如牛毛,每个人都用自己的标准来配置文件或者设置目录,那么未来的Linux则就是一团乱麻,也对管理造成许多麻烦。后来,就有所谓的FHS(F...

Linux命令,这些操作要注意!(linux命令?)

刚玩Linux的人总觉得自己在演黑客电影,直到手滑输错命令把公司服务器删库,这才发现命令行根本不是随便乱用的,而是“生死簿”。今天直接上干货,告诉你哪些命令用好了封神!喜欢的一键三连,谢谢观众老爷!!...

Linux 命令速查手册:这 30 个高频指令,拯救 90% 的运维小白!

在Linux系统的世界里,命令行是强大的武器。对于运维小白而言,掌握一些高频使用的Linux命令,能极大提升工作效率,轻松应对各种系统管理任务。今天,就为大家奉上精心整理的30个Linu...

linux必学的60个命令(linux必学的20个命令)

以下是Linux必学的20个基础命令:1.cd:切换目录2.ls:列出文件和目录3.mkdir:创建目录4.rm:删除文件或目录5.cp:复制文件或目录6.mv:移动/重命名文件或目录7....

提高工作效率的--Linux常用命令,能够决解95%以上的问题

点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...

15 个实用 Linux 命令(linux命令用法及举例)

Linux命令行是系统管理员、开发者和技术爱好者的强大工具。掌握实用命令不仅能提高效率,还能解锁Linux系统的无限潜力,本文将深入介绍15个实用Linux命令。ls-列出目录内容l...

Linux 常用命令集合(linux常用命令全集)

系统信息arch显示机器的处理器架构(1)uname-m显示机器的处理器架构(2)uname-r显示正在使用的内核版本dmidecode-q显示硬件系统部件-(SMBIOS/DM...

Linux的常用命令就是记不住,怎么办?

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux常用文件操作命令(linux常用文件操作命令有哪些)

ls命令在Linux维护工作中,经常使用ls这个命令,这是最基本的命令,来写几条常用的ls命令。先来查看一下使用的ls版本#ls--versionls(GNUcoreutils)8.4...

Linux 常用命令(linux常用命令)

日志排查类操作命令查看日志cat/var/log/messages、tail-fxxx.log搜索关键词grep"error"xxx.log多条件过滤`grep-E&#...

简单粗暴收藏版:Linux常用命令大汇总

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部下午好,我的网工朋友在Linux系统中,命令行界面(CLI)是管理员和开发人员最常用的工具之一。通过命令行,用户可...

「Linux」linux常用基本命令(linux常用基本命令和用法)

Linux中许多常用命令是必须掌握的,这里将我学linux入门时学的一些常用的基本命令分享给大家一下,希望可以帮助你们。总结送免费学习资料(包含视频、技术学习路线图谱、文档等)1、显示日期的指令:d...

Linux的常用命令就是记不住,怎么办?于是推出了这套教程

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux的30个常用命令汇总,运维大神必掌握技能!

以下是Linux系统中最常用的30个命令,精简版覆盖日常操作核心需求,适合快速掌握:一、文件/目录操作1.`ls`-列出目录内容`ls-l`(详细信息)|`ls-a`(显示隐藏文件)...

Linux/Unix 系统中非常常用的命令

Linux/Unix系统中非常常用的命令,它们是进行文件操作、文本处理、权限管理等任务的基础。下面是对这些命令的简要说明:**文件操作类:*****`ls`(list):**列出目录内容,显...