透过源码理解Flutter InheritedWidget

news2024/12/28 19:50:54

 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 - 知乎

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Android AIDL] --- AIDL工程搭建

0 AIDL概念 AIDL&#xff08;Android Interface Definition Language&#xff09;是一种 IDL 语言&#xff0c;用于生成可以在 Android 设备上两个进程之间进行进程间通信&#xff08;IPC&#xff09;的代码。 通过 AIDL&#xff0c;可以在一个进程中获取另一个进程的数据和调…

视频汇聚/视频云存储/视频监控管理平台EasyCVR接入海康SDK协议后无法播放该如何解决?

开源EasyDarwin视频监控/安防监控/视频汇聚EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放&#xff0c;可同时播放多路视频流&#…

idea自定义提示关键词 switch

idea中希望提示Switch case的快捷键&#xff0c; 比如我想自定义一个输入【mmm】就提示main方法&#xff0c;该怎么做&#xff1f; 1.File - Settings&#xff0c;点击。 2.找Live Templates &#xff0c;可以在搜索里面搜live&#xff0c;找到后点击。 3.先点击号&#xff…

系统架构设计师-计算机系统基础知识(2)

目录 一、存储管理 1、页式存储 2、段式存储 3、段页式存储 二、磁盘管理 1、先来先服务FCFS 2、最短寻道时间优先SSTF 三、文件系统 1、文件基本概念 2、文件的类型&#xff1a; 3、索引文件结构 4、位示图 一、存储管理 1、页式存储 将程序与内存划分为同样大小的块&…

算法面试-深度学习面试题整理(2024.8.29开始,每天下午持续更新....)

一、无监督相关&#xff08;聚类、异常检测&#xff09; 1、常见的距离度量方法有哪些&#xff1f;写一下距离计算公式。 1&#xff09;连续数据的距离计算&#xff1a; 闵可夫斯基距离家族&#xff1a; 当p 1时&#xff0c;为曼哈顿距离&#xff1b;p 2时&#xff0c;为欧…

C++中的 class和struct区别

C 中保留了C语言的 struct 关键字&#xff0c;并且加以扩充。在C语言中&#xff0c;struct 只能包含成员变量&#xff0c;不能包含成员函数。而在C中&#xff0c;struct 类似于 class&#xff0c;既可以包含成员变量&#xff0c;又可以包含成员函数。 C中的 struct 和 class 基…

领英采用 Protobuf 进行微服务开发,网络延迟降低60%

领英采用 Protobuf&#xff0c;以实现其各类平台中更为高效的微服务间数据传递&#xff0c;并将其与开源框架 Rest.li 相集成。在全公司范围的推广完成后&#xff0c;领英将延迟降低了 60%的同时&#xff0c;也提高了资源的利用率。 领英平台所采用的是微服务架构&#xff0c;…

InnoDB表空间

一、页面类型 1.1 页面通用部分 File Header &#xff1a;记录页面的一些通用信息 File Trailer &#xff1a;校验页是否完整&#xff0c;保证从内存到磁盘刷新时内容的一致性。 File Header结构&#xff1a; 二、独立表空间结构 2.1 区 对于16KB的页来说&#xff0c;连续的…

android logcat问题 怎么换成旧版

参考 如果想切换回旧版LOGCAT&#xff0c;按照下方步骤设置即可 File->Settings->Expermental->Logcat->Enable new Logcat tool window&#xff1a;取消勾选 设置好后上方会有一个Toast&#xff0c;询问你是否使用新版logcat&#xff0c;关掉即可 最新测试版移…

如何能使mp3的音量变大?

如何能使mp3的音量变大&#xff1f;我们经常在日常生活中使用的一种音频格式是MP3。许多朋友在下载音乐后&#xff0c;都会选择MP3格式进行播放。然而&#xff0c;在我们的日常生活中&#xff0c;我们有时会遇到音量太小的问题。这时候&#xff0c;我们听歌可能会感到很不舒服。…

分段三次hermit插值

保形三次hermit插值 一、算法实现 一、插值函数建立 设函数 y F ( x ) yF(x) yF(x)在区间 [ a , b ] [a,b] [a,b]上有定义&#xff0c;且已知在离散点 a x 0 < x 1 < . . . < x n b ax_0<x_1<...<x_n b ax0​<x1​<...<xn​b上的值 y 0 , y…

关于Maxwell与Kafka和数据库的监控

1.Maxwell的配置 其实就是配置两端的配置信息,都要能连接上,然后才能去传输数据 config.properties #Maxwell数据发送目的地&#xff0c;可选配置有stdout|file|kafka|kinesis|pubsub|sqs|rabbitmq|redis producerkafka # 目标Kafka集群地址 kafka.bootstrap.servershadoop102…

立创EDA专业版的原理图上器件有一个虚线框

立创EDA专业版的原理图上器件有一个虚线框解决方法 问题分析&#xff1a; 在使用立创EDA专业版 设计电路原理图时&#xff0c;中途莫名其妙就给我的元件添加了下面图片所示的虚线外框。看着就很别扭的样子&#xff0c;而且工程大了和器件稍微布局比较密的时候就导致整体很难看…

Python+selenium等待某个元素消失(如加载中)再继续执行代码

&#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的双手 &#x1f3f3;️‍&#x1f308; 博客主页&#xff1a;一晌小贪欢的博客主页 &#x1f44d; 该系列文章专栏&#xff1a;Python办公自动化专栏 文章作者技术和水平有限&#xff0c…

【ASP.NET】LIS实验室信息管理系统源码

LIS系统&#xff0c;即实验室信息管理系统&#xff0c;是一种基于互联网技术的医疗行业管理软件&#xff0c;它可以帮助实验室进行样本管理、检测流程管理、结果报告等一系列工作&#xff0c; 提高实验室工作效率和质量。 一、LIS系统的功能 1. 样本管理 LIS系统可以帮助实验…

用反射实现自定义Java对象转化为json工具类

传入一个object类型的对象获取该对象的class类getFields方法获取该类的所有属性对属性进行遍历&#xff0c;并且拼接成Json格式的字符串&#xff0c;注意&#xff1a;通过属性名来推断方法名获取Method实例通过invoke方法调用 public static String objectToJsonUtil(Object o…

轻松搭建微信小程序商城的详细指南

微信小程序商城是目前非常流行的一种电商模式&#xff0c;它能够为用户提供便捷的购物体验&#xff0c;同时也成为了很多企业开展电商业务的首选方式。那么&#xff0c;如何快速、简单地搭建一个微信小程序商城呢&#xff1f;下面就为大家介绍一下详细的步骤。 首先&#xff0c…

分布式系统,你了解多少呢

本期只是简单了解&#xff0c;想要深入学习&#xff0c;还需要看看其他资源~ 目录 一、单机架构 二、数据库和应用分离 三、负载均衡——应用服务器 四、读写分离——数据库主从结构 五、引入缓存——冷热数据分离 六、分库分表——数据库扩展空间 七、微服务——进一步…

性能测试常见的测试指标

一、什么是性能测试 先看下百度百科对它的定义 性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。我们可以认为性能测试是&#xff1a;通过在测试环境下对系统或构件的性能进行探测&#xff0c;用以验证在生产环境下系统性能…

Rtab-map

Rtab-map -- 开源 2014 about 如何实现闭环检测 可视化slam 应用于摄像头上 原理&#xff1a;数据结构&#xff0c;点中存储彩色图&#xff0c;深度图&#xff0c;和用来做odomtry得位置&#xff0c;可以认为图片&#xff0c;location和image都是点 将图片解析为一些feature…