透过源码理解Flutter InheritedWidget

 InheritedWidget的核心是保存值和保存使用这个值的widget,通过对比值的变化,来决定是否要通知那些使用了这个值的widget更新自身。

1 updateShouldNotify和notifyClients

InheritedWidget通过updateShouldNotify函数控制依赖其的子组件是否在InheritedWidget变化时会被重建:如果updateShouldNotify返回true,InheritedWidget变化时子组件的build会被调用,反之则不会。

InheritedElement中的updated方法:

@override
void updated(InheritedWidget oldWidget) {
  if (widget.updateShouldNotify(oldWidget))
    super.updated(oldWidget);
}

InheritedElement继承于ProxyElement,而ProxyElement的updated实现为:

@protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

而InheritedElement的notifyClients实现是遍历_dependents中依赖自己的widget,然后调用它的didChangeDependencies进行变化通知:

  @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      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));
      notifyDependent(oldWidget, dependent);
    }
  }

@protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }
Element的didChangeDependencies源码如下:

void didChangeDependencies() {
  assert(_active); // otherwise markNeedsBuild is a no-op
  assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
  markNeedsBuild();
}

可以看到,InheritedWidget变化时,如果updateShouldNotify是true,会通过notifyClients调用子组件的didChangeDependencies函数,从而调用markNeedsBuild,将本Element加入_dirtyElements列表中。大家都知道,_dirtyElements中保存的是需要重建的Element,会在下一帧时被rebuild,因此在下一帧子组件会被重建(rebuild)。

2 InheritedWidget的传递

InheritedWidget能用于让用户快速从子组件获取,这是怎么实现的呢?

其实Flutter Framework也是将InheritedWidget一层层传递下来的,只不过由于Framework层自行处理了,因此这个过程对于我们是透明的。我们现在来梳理下InheritedWidget传递的过程。

Element中,有一个map:_inheritedWidgets。保存了所有上级节点中的InheritedElement。其源码如下:

Map<Type, InheritedElement> _inheritedWidgets;

其中,key中Type是InheritedWidget的子类,value是InheritedElement。为什么这里value保存的是InheritedElement而不是InheritedWidget呢?由以前的文章可以知道Element中保存着对应Widget的引用,因此可以通过InheritedElement获取对应的InheritedWidget。而且Widget在上级Widget重建时会被重建,因此保存InheritedElement更合适。

在普通的Element中,_inheritedWidgets会直接复制其父组件中_inheritedWidgets的值,其源码如下:

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

而在InheritedElement中,_inheritedWidgets会首先复制其父组件中_inheritedWidgets的值,然后将自己添加进列表,其源码如下:

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

由此可以看出,InheritedElement就是这样一层层传递下来的。_inheritedWidgets赋值流程如下:

由该流程图可以看出,_inheritedWidgets在Element被加入Element Tree时就已经被赋值,因此其在子组件的build函数中是可以访问得到的。

3 InheritedWidget的获取及注册依赖

我们已经知道了InheritedElement会传递到下级组件中,那怎么获取它呢?Flutter提供了专门获取某个InheritedWidget类型的函数dependOnInheritedWidgetOfExactType.其源码如下:

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

由第三行可以看出,此函数会从_inheritedWidgets中寻找对应的InheritedElement,并返回其InheritedWidget。

除了dependOnInheritedWidgetOfExactType,Flutter还提供了另一个专门获取某个InheritedWidget类型的函数:getElementForInheritedWidgetOfExactType。其源码如下:

@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  return ancestor;
}

对比其与dependOnInheritedWidgetOfExactType源码,可以看到dependOnInheritedWidgetOfExactType多了dependOnInheritedElement函数的调用,该函数用于创建InheritedWidget和调用dependOnInheritedWidgetOfExactType的组件的依赖关系。其有两个步骤:

  • 将依赖的InheritedElement加入本Element的_dependencies列表,该列表中保存了本Element所有依赖的InheritedElement.
  • 将本Element加入依赖的InheritedElement的_dependents map,该列表中保存了所有依赖该InheritedElement的Element。

如果使用的是dependOnInheritedWidgetOfExactType,则当被依赖的InheritedWidget被更新时,依赖的子组件会被rebuild;而使用的是getElementForInheritedWidgetOfExactType时,由于不会建立相应的依赖关系,InheritedWidget被更新时,依赖的子组件不会被rebuild。

4 主动调用dependOnInheritedWidgetOfExactType

 子组件需要通过调用dependOnInheritedWidgetOfExactType来获取InheritedWidget,并且将自己加入该InheritedWidget的依赖中。方便起见,一般会在InheritedWidget子类中实现of方法:

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) {
    print("__TestWidgetState build");
    //使用InheritedWidget中的共享数据
    return Text(ShareDataWidget
        .of(context)
        .data
        .toString());
    // return Text("tex");
  }

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

参考:

Flutter框架分析 -InheritedWidget - 知乎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/100143.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

4.22 TCP 四次挥手,可以变成三次吗?

目录 为什么 TCP 挥手需要四次呢&#xff1f; 粗暴关闭 vs 优雅关闭 close函数 shotdown函数 什么情况会出现三次挥手&#xff1f; 什么是 TCP 延迟确认机制&#xff1f; TCP 序列号和确认号是如何变化的&#xff1f; 在一些情况下&#xff0c; TCP 四次挥手是可以变成 T…

FFmpeg支持多线程编码并保存mp4文件示例

之前介绍的示例&#xff1a; (1).https://blog.csdn.net/fengbingchun/article/details/132129988 中对编码后数据保存成mp4 (2).https://blog.csdn.net/fengbingchun/article/details/132128885 中通过AVIOContext实现从内存读取数据 (3).https://blog.csdn.net/fengbingchun/…

一百六十八、Kettle——用海豚调度器定时调度从Kafka到HDFS的任务脚本(持续更新追踪、持续完善)

一、目的 在实际项目中&#xff0c;从Kafka到HDFS的数据是每天自动生成一个文件&#xff0c;按日期区分。而且Kafka在不断生产数据&#xff0c;因此看看kettle是不是需要时刻运行&#xff1f;能不能按照每日自动生成数据文件&#xff1f; 为了测试实际项目中的海豚定时调度从…

Java设计模式:四、行为型模式-09:模板模式

文章目录 一、定义&#xff1a;模板模式二、模拟场景&#xff1a;模板模式三、改善代码&#xff1a;模板模式3.0 引入依赖3.1 工程结构3.2 模板模式结构图3.3 爬取商品生成海报实现3.3.1 HTTP获取连接类3.3.2 定义执行顺序的抽象类3.3.3 当当爬取抽象实现类3.3.4 京东爬取抽象实…

切换Java版本

Mac安装不同Java版本 在Sentinel限流框架的使用中&#xff0c;Java版的Sentinel提供一个可以起Dashboard的jar包。访问项目接口&#xff0c;按预期应该在Dashboard里有数据。发现多次请求后还是空白。 仔细看Dashboard的日志&#xff0c;疑似是Java版本的问题&#xff0c;搜了下…

无涯教程-机器学习 - 箱形图函数

Box和Whisker图(也简称为boxplots)是另一种有用的技术&#xff0c;可用于检查每个属性的分布情况。以下是此技术的特点- 它本质上是单变量的&#xff0c;总结了每个属性的分布。它为中间值(即中位数)画一条线。它将在25&#xff05;和75&#xff05;周围绘制一个框。它还会绘制…

SAP PP之定义活动/作业类型(Activity Type)

文章目录 前言 一、作业是什么 二、使用步骤 1.单独创建 2.创建组 注意点 前言 创建活动类型具有以下先决条件&#xff1a; 控制范围已创建并分配给公司代码。已创建成本要素类别为43的次要成本要素。 一、作业是什么 SAP活动类型是在成本范围的成本中心中产生的活动的分类。…

css强制显示一行

要强制将文本内容显示在一行中&#xff0c;可以使用CSS的white-space属性和overflow属性来实现。 首先&#xff0c;将white-space属性设置为nowrap&#xff0c;这样文本内容就不会换行。然后&#xff0c;将overflow属性设置为hidden&#xff0c;这样超出一行的内容就会被隐藏起…

BDCC - 闲聊数据仓库的架构

文章目录 典型数据仓库架构图数据仓库ETL vs ELTETLELT区别联系 数据仓库分层&#xff08;1&#xff09;数据仓库ODS层&#xff08;2&#xff09;数据仓库CDM层DWD数据明细层DWS数据汇总层 &#xff08;3&#xff09;数据仓库ADS层 典型数据仓库架构图 按自下而上的顺序&#x…

《Python趣味工具》——其他常见的RPG游戏梳理:

Hello&#xff0c;各位朋友们大家好&#xff01;昨天我们一起制作了自己的第一个RPG游戏——《人生选择模拟器》&#xff0c;是不是还意犹未尽呢&#xff1f;哈哈&#xff0c;今天我们再来尝试做几款比较轻量级的小游戏吧&#xff01; 文章目录 1. 猜单词游戏:2. 姻缘测试:3. …

通过这 5 项 ChatGPT 创新增强您的见解

为什么绝大多数的人还不会使用chatGPT来提高工作效能&#xff1f;根本原因就在还不会循序渐进的发问与chatGPT互动。本文总结了5个独特的chatGPT提示&#xff0c;可以帮助您更好地与Chat GPT进行交流&#xff0c;以获得更清晰的信息、额外的信息和见解。 澄清假设和限制 用5种提…

Vue前端的一些表格组件的思考

当我们需要在前端中展示一些表格内容时&#xff0c;我们往往使用Vue的table来实现 1. 原生态实现 <template><div><table class"no-gap-table"><thead><tr><th class"styled-header" colspan"4">Column1&…

Linux 操作系统实战视频课 - GPIO 基础介绍

文章目录 一、GPIO 概念说明二、视频讲解沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将讲解 GPIO 。 一、GPIO 概念说明 ARM 平台中的 GPIO(通用输入/输出)是用于与外部设备进行数字输入和输出通信的重要硬件接口。ARM 平台的 GPIO 特性可以根据具体的芯…

六、高并发内存池--Central Cache

六、高并发内存池–Central Cache 6.1 Central Cache的工作原理 central cache也是一个哈希桶结构&#xff0c;他的哈希桶的映射关系跟thread cache是一样的。不同的是他的每个哈希桶位置挂是SpanList链表结构&#xff0c;不过每个映射桶下面的span中的大内存块被按映射关系切…

IPV4地址说明

设想一个场景&#xff1a; 你有两台电脑A和B&#xff0c;需要把A的数据传输到B&#xff0c;怎么办&#xff1f; 1 我们可以用U盘进行拷贝&#xff0c;就是把A的数据拷贝到B 2 我们可以用一根网线把AB连接起来 显然&#xff0c;两台电脑用一根网线。那要是n台电脑呢&#xff1f;…

进程管理死死的学

进程管理 文件属性 chattr【扩展】 chattr chattr i 文件名 # 添加权限 a 可追加&#xff0c;不可修改 i 只可查看 A 不修改访问时间 charrt -i 文件名 # 取消权限 -R 递归处理&#xff0c;将指令目录下的所有文件及子目录一并处理&#xff1b;lsattr 查看文件属性 lsattr …

事务的总结

数据库事务 数据库事务是一个被视为单一的工作单元的操作序列。这些操作应该要么完整地执行&#xff0c;要么完全不执行。事务管理是一个重要组成部分&#xff0c;RDBMS 面向企业应用程序&#xff0c;以确保数据完整性和一致性。事务的概念可以描述为具有以下四个关键属性描述…

js对中文进行base64编码和解码操作,解决中文乱码问题

我使用github api的接口获取文件内容&#xff0c;然后使用atob进行解码&#xff0c;但是发现&#xff1a;乱码.......糟心啊 所以就有了我封装的方法&#xff1a; export const encode64 (str) > {// 首先&#xff0c;我们使用 encodeURIComponent 来获得百分比编码的UTF…

Python数据分析案例30——中国高票房电影分析(爬虫获取数据及分析可视化全流程)

案例背景 最近总看到《消失的她》票房多少多少&#xff0c;《孤注一掷》票房又破了多少多少..... 于是我就想自己爬虫一下获取中国高票房的电影数据&#xff0c;然后分析一下。 数据来源于淘票票&#xff1a;影片总票房排行榜 (maoyan.com) 爬它就行。 代码实现 首先爬虫获…

Django传递dataframe对象到前端网页

在django前端页面上展示的数据&#xff0c;还是使用django模板自带的语法 方式1 不推荐使用 直接使用 【df.to_html(indexFalse)】 使用to_html他会生成一个最基本的表格没有任何的样式&#xff0c;一点都不好看&#xff0c;如果有需要的话可以自行修改表格的样式&#xff0c;…