Flutter视图原理之StatefulWidget,InheritedWidget

目录

    • StatefulElement
      • 1. 构造函数
      • 2. build
      • 3. _firstBuild
      • 3. didChangeDependencies
      • 4. setState
    • InheritedElement
      • 1. Element类
      • 2. _updateInheritance
      • 3. InheritedWidget数据向下传递
        • 3.1 dependOnInheritedWidgetOfExactType
      • 4. InheritedWidget的状态绑定
        • 4.1. ProxyElement

在flutter项目中,StatelessWidget,StatefulWidget,InheritedWidget是常见的widget,今天通过源码分析下它们是怎么实现的。

对应的功能基本上都是在element中实现的,widget只是提供组件配置的作用,所以在讲解StatefulWidget,InheritedWidget的时候,主要还是分析对应的element的实现。

StatefulElement

StatefulWidget是带有状态的Widget,和statelessWidget不同,Widget的创建是委托给state创建的,而不是使用widget.build直接创建的。
StatelessWidget代码上一章 三棵树的建立过程 已经讲过了,忽略。

在这里插入图片描述) 在这里插入图片描述)

1. 构造函数

对比statelessElement的构造函数:

class StatefulElement extends ComponentElement {/// Creates an element that uses the given widget as its configuration.StatefulElement(StatefulWidget widget): _state = widget.createState(),super(widget) {assert(state._element == null);state._element = this;state._widget = widget;assert(state._debugLifecycleState == _StateLifecycle.created);}//...省略
}

在构造函数中,直接调用了widget.createState().新建了state,state的成员变量有element,widget都是私有成员,这个时候state的生命周期应该是created的,state._debugLifecycleState == _StateLifecycle.created

2. build

使用state来创建widget:

  Widget build() => state.build(this);

3. _firstBuild

void _firstBuild() {//...省略state.didChangeDependencies();assert(() {state._debugLifecycleState = _StateLifecycle.ready;}());super._firstBuild();}

这个函数调用,发生在第一次生成element的时候,该element被mount的时候触发,这个时候会回调state的didChangeDependencies方法。
还有一种情况是performRebuild()的时候有可能会回调,下面会讲到。

再看这幅图:
在这里插入图片描述

3. didChangeDependencies

didChangeDependencies函数是element的回调接口,这个接口是在依赖项更改的时候被parent通知调用的,会修改_didChangeDependencies = true;,然后performRebuild()函数会触发state.didChangeDependencies();的回调。

  bool _didChangeDependencies = false;void didChangeDependencies() {super.didChangeDependencies();_didChangeDependencies = true;}void performRebuild() {if (_didChangeDependencies) {state.didChangeDependencies();_didChangeDependencies = false;}super.performRebuild();}

4. setState

void setState(VoidCallback fn) {//。。。省略final Object? result = fn() as dynamic;assert(() {if (result is Future) {throw FlutterError.fromParts(<DiagnosticsNode>[]);}return true;}());_element!.markNeedsBuild();}

可以看到setstate的参数不能是返回future的回调,然后调用element的markNeedsBuild方法,通知element重新build一次。
重新build的时候,如果满足element可以复用旧的,但是需要更新newWidget的情况下,会触发state.didUpdateWidget(方法,也就对应的上图生命周期了。

注:上一章 三棵树的建立过程 已经讲过其他的函数,这里忽略。

InheritedElement

InheritedWidget本质有两大功能,

  1. InheritedWidget数据向下传递(下层节点可以获取到InheritedWidget中的数据)
  2. InheritedWidget的状态绑定(就是InheritedWidget被修改,会导致引用的地方数据刷新)

这些功能都是在Element和InheritedElement中实现的。
在这里插入图片描述在这里插入图片描述

1. Element类

element是基类,是所有子类的基础实现,它内部有这几个成员变量让inheritedElement功能得以实现:

  PersistentHashMap<Type, InheritedElement>? _inheritedElements;Set<InheritedElement>? _dependencies;bool _hadUnsatisfiedDependencies = false;

_inheritedElements这个保存着这棵树的所有inheritedElement类型元素(如果自己也是,那么自己也会加入到这个集合中);
_dependencies保存着当前element所依赖的祖先InheritedElement;
_hadUnsatisfiedDependencies 当按照类型查找祖先InheritedElement没找到,那么这个变量会设置成true,下次active激活页面的时候,会通知build一次。

2. _updateInheritance

element的实现如下:

  void mount(Element? parent, Object? newSlot) {//省略_updateInheritance();attachNotificationTree();}void _updateInheritance() {assert(_lifecycleState == _ElementLifecycle.active);_inheritedElements = _parent?._inheritedElements;}

在当前element挂载到树中的时候,会主动更新一次_inheritedElements 元素,从parent当中获取_inheritedElements 集合。

InheritedElement复写了这个方法:

  void _updateInheritance() {assert(_lifecycleState == _ElementLifecycle.active);final PersistentHashMap<Type, InheritedElement> incomingWidgets =_parent?._inheritedElements ?? const PersistentHashMap<Type, InheritedElement>.empty();_inheritedElements = incomingWidgets.put(widget.runtimeType, this);}

从parent获取了_inheritedElements 集合之后,还需要将自己加入到这个集合中。

3. InheritedWidget数据向下传递

3.1 dependOnInheritedWidgetOfExactType

dependOnInheritedWidgetOfExactType这个方法,通常使用的情况是,子widget的state中去获取全局的element。如下,获取这个主题元素CupertinoThemeData 的时候,调用了of方法,里面就是调用的dependOnInheritedWidgetOfExactType。

  static CupertinoThemeData of(BuildContext context) {final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>();return (inheritedTheme?.theme.data ?? const CupertinoThemeData()).resolveFrom(context);}

然后在系统的buttonWidget中使用了这个主题:
在这里插入图片描述

我们接着看下dependOnInheritedWidgetOfExactType到底做了什么事情:

  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {assert(_debugCheckStateIsActiveForAncestorLookup());final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];if (ancestor != null) {return dependOnInheritedElement(ancestor, aspect: aspect) as T;}_hadUnsatisfiedDependencies = true;return null;}
  1. 从_inheritedElements集合中,按照key来查询element元素;
  2. 查找到目标ancestor之后,需要和祖先进行关系绑定dependOnInheritedElement
  3. 如果查找不到,那么将_hadUnsatisfiedDependencies 置为true。

dependOnInheritedElement

  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {_dependencies ??= HashSet<InheritedElement>();_dependencies!.add(ancestor);ancestor.updateDependencies(this, aspect);return ancestor.widget as InheritedWidget;}

首先是将祖先element加入到_dependencies集合中,这样该element就知道自己依赖了哪几个祖先element,祖先也调用updateDependencies更新祖先的依赖,然后将祖先返回。

updateDependencies

祖先是inheritedElement,它实现了updateDependencies方法,将子element加入到map中。

  final Map<Element, Object?> _dependents = HashMap<Element, Object?>();void updateDependencies(Element dependent, Object? aspect) {setDependencies(dependent, null);}void setDependencies(Element dependent, Object? value) {_dependents[dependent] = value;}

通过上面的步骤,子element和祖先inheritedElement,产生了相互依赖关系。
实现了InheritedWidget数据向下传递(下层节点可以获取到InheritedWidget中的数据)

4. InheritedWidget的状态绑定

4.1. ProxyElement

proxyElement复写了两个接口:

widget的创建是返回的子child

  Widget build() => (widget as ProxyWidget).child;

update更新widget,需要先调用updated,在调用build方法

  void update(ProxyWidget newWidget) {final ProxyWidget oldWidget = widget as ProxyWidget;super.update(newWidget);updated(oldWidget);rebuild(force: true);}

proxyElement新增了两个接口:

	//widget已经更新过了,需要通知依赖项void updated(covariant ProxyWidget oldWidget) {notifyClients(oldWidget);}//通知接口,具体需要子类实现void notifyClients(covariant ProxyWidget oldWidget);

inheritedElement复写接口:

  void updated(InheritedWidget oldWidget) {if ((widget as InheritedWidget).updateShouldNotify(oldWidget)) {super.updated(oldWidget);}}void notifyClients(InheritedWidget oldWidget) {assert(_debugCheckOwnerBuildTargetExists('notifyClients'));for (final Element dependent in _dependents.keys) {assert(() {// check that it really is our descendantElement? ancestor = dependent._parent;while (ancestor != this && ancestor != null) {ancestor = ancestor._parent;}return ancestor == this;}());// check that it really depends on usassert(dependent._dependencies!.contains(this));notifyDependent(oldWidget, dependent);}}
  1. 先判断updateShouldNotify接口需要根据widget来判断,是否通知依赖集合
  2. 如果需要通知,那么遍历_dependents集合,首先验证是否是自己的子孙,接着验证子element._dependencies集合是否有parent;
  3. 接着通知依赖的子项,调用dependent.didChangeDependencies();

element实现:

  void didChangeDependencies() {assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-opassert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));markNeedsBuild();}

通知子child的element需要重新build了,因为子child依赖了parent的数据,parent的数据发生变化的时候,是需要强制子child去重新build的。

statefulElemenr实现:

  bool _didChangeDependencies = false;void didChangeDependencies() {super.didChangeDependencies();_didChangeDependencies = true;}

除了调用super方法,还将_didChangeDependencies 设置为true;上面说过performRebuild()调用的时候,如果这个标志是true的话,会通知state.didChangeDependencies();接口,对应上了上面图片所示的生命周期函数回调。

inheritedElement功能流程图:

在这里插入图片描述

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

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

相关文章

.net6部署到linux上(CentOS Linux 7)

目录 一、先在linux上配置.net环境 添加 Microsoft 包存储库 安装 SDK 安装运行时 检查 SDK 版本可使用终端查看当前安装的 .NET SDK 版本。 打开终端并运行以下命令。 二、创建.net6 mvc项目 并发布 创建项目 修改默认端口 打包发布到文件夹 运行打包项目查看项目是否…

macOS telnet替代方式

前言 经过使用Linux&#xff0c;常常用Linux的telnet查看端口畅通&#xff0c;是否有防火墙&#xff0c;但是在mac上已经没有这个命令了&#xff0c;那么怎么使用这个命令或者有没有其他替代呢&#xff0c;win和linux是否可以使用相同的替代。macOS可以原生用nc命令替代&#…

C算法:使用选择排序实现从(大到小/从小到大)排序数组,且元素交换不可使用第三变量。

需求&#xff1a; 使用选择排序实现从(大到小/从小到大)排序&#xff0c;且元素交换不可使用第三变量 (异或交换法) 代码实现&#xff1a; #include <stdio.h> void maopao(int* array,int len,int(*swap)(int a,int b)) {int i,j;for(i0;i<len-1;i){for(ji1;j<…

【数据结构与算法】two X 树的遍历以及功能实现

前言&#xff1a; 前面我们已经提到过树、二叉树的概念及结构、堆排序、Top-k问题等的知识点&#xff0c;这篇文章我们来详解一下二叉树的链式结构等问题。 &#x1f4a5;&#x1f388;个人主页:​​​​​​Dream_Chaser&#xff5e; &#x1f388;&#x1f4a5; ✨✨专栏:htt…

【智能家居】

面向Apple developer学习&#xff1a;AirPlay | Apple Developer Documentation Airplay AirPlay允许人们将媒体内容从iOS、ipad、macOS和tvOS设备无线传输到支持AirPlay的Apple TV、HomePod以及电视和扬声器上。 网页链接的最佳实践 首选系统提供的媒体播放器。内置的媒体播…

SpringCloud和Kubernetes的区别

又见小道仙&#xff1a; https://blog.csdn.net/Tomwildboar/article/details/129531315 对于SpringCloud在实际项目中并未使用过&#xff0c;只是自学过SpringCloud和SpringCloud Alibaba&#xff0c;也基于学习搭建过demo。 对于Kubernetes&#xff0c;目前这家公司就是使用…

Web APIs——事件监听以及案例

1、事件监听 什么是事件&#xff1f; 事件是在编程时系统内发生的动作或者发生的事情 比如用户在网页上单击一个按钮 什么是事件监听&#xff1f; 就是让程序检测是否有事件产生&#xff0c;一旦有事件触发&#xff0c;就立即调用一个函数做出响应&#xff0c;也称为绑定事…

【RocketMQ集群】Linux搭建RocketMQ双主双从集群

在当今大数据时代&#xff0c;消息队列系统成为了构建高可用、可扩展和可靠的分布式应用的重要组件之一。而Apache RocketMQ作为一款开源的分布式消息中间件&#xff0c;以其高吞吐量、低延迟和可靠性而备受青睐。为了满足大规模应用的需求&#xff0c;搭建RocketMQ集群是一种常…

基于springboot实现基于Java的超市进销存系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现基于Java的超市进销存系统演示 摘要 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;超市进销存系统也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#x…

Weights and Biases使用教程

Weights and Biases使用教程 安装和初始化实验跟踪跟踪指标跟踪超参数可视化模型检查日志 数据和模型版本控制使用Sweeps进行超参数调优数据可视化report Weights and Biases已经成为AI社区中最受欢迎的库之一。该团队在创建了一个平台&#xff0c;使机器深度学习学习工程师能够…

云安全—docker原理

0x00 前言 因为要学习docker相关的检测技术&#xff0c;所以需要对docker的原理进行基本的原因&#xff0c;不求彻底弄懂&#xff0c;但求懂点皮毛&#xff0c;如有不妥之处&#xff0c;还请斧正。 0x01 docker概述 docker起源 docker公司是在旧金山&#xff0c;由法裔美籍…

Java操作Elasticsearch(新增数据)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

广告掘金全自动挂机项目,单设备30+【软件脚本+技术教程】

广告掘金项目是一种越来越受欢迎的赚钱方式&#xff0c;它通过观看广告视频来获取收益。然而&#xff0c;手动观看每个广告视频可能会耗费大量时间和精力。为了简化操作并提升效率&#xff0c;我们可以利用全自动挂机脚本来完成这一任务。接下来&#xff0c;将为您介绍如何使用…

PLC单按钮启停算法汇总

单按钮启停在三菱PLC里可以通过简单的取反指令"ALT"实现,西门子PLC如何实现ALT指令,请参考下面文章链接,这篇博客我们汇总常用的单按钮启停实现方法,希望大家读了本篇博客后有所收获。 博途ALT指令 博途S7-1200/1500PLC 取反指令(ALT)-CSDN博客SMART PLC的ALT指…

安卓 实现60s倒计时的CountDownTimer(小坑)

安卓 实现60s倒计时的CountDownTimer&#xff08;小坑&#xff09; 前言一、CountDownTimer 是什么&#xff1f;二、代码示例1.使用2.小坑的点误差及时取消 总结 前言 前段时间写倒计时没有用线程&#xff0c;想换一种实现方式结果踩了个小坑&#xff0c;特此记录。 一、Count…

pymavlink简单使用

环境 windows10 python3.11 pymavlink2.4.40 工具 虚拟串口工具 Virtual Serial Port Driver 6.9 串口/网络调试助手 sscom5.13.1.exe 串口通信 虚拟串口 COM1, COM2 调试助手连接COM2 pymavlink连接COM1 from pymavlink import mavutil import timeconnect mavut…

LabVIEW应用开发——控件的使用(三)

接上文&#xff0c;这篇介绍簇Cluster控件。 LabVIEW应用开发——控件的使用&#xff08;二&#xff09; 1、簇Cluster 1&#xff09;创建 蔟控件又称为组合、集群控件&#xff0c;顾名思义它是一个类似于C语言的结构体的一个数据结构控件。在描述一个对象的时候&#xff0c;…

SpringCloud 微服务全栈体系(三)

第五章 Nacos 注册中心 国内公司一般都推崇阿里巴巴的技术&#xff0c;比如注册中心&#xff0c;SpringCloudAlibaba 也推出了一个名为 Nacos 的注册中心。 一、认识和安装 Nacos 1. 认识 Nacos Nacos是阿里巴巴的产品&#xff0c;现在是SpringCloud中的一个组件。相比Eure…

“创新启变 聚焦增长”极狐(GitLab)媒体沟通会,共话智能时代软件开发新生态

10 月 18 日 北京 昨日&#xff0c;全球领先 AI 赋能 DevSecOps 一体化平台极狐(GitLab) 在北京举办了主题为“创新启变 聚焦增长”的媒体沟通会。极狐(GitLab) CEO 柳钢就“中国企业数字化转型、软件研发、技术自主可控等热点问题&#xff0c;以及 AI 大模型时代下&#xff0c…

论文阅读 - Coordinated Behavior on Social Media in 2019 UK General Election

论文链接&#xff1a; https://arxiv.org/abs/2008.08370 目录 摘要&#xff1a; Introduction Contributions Related Work Dataset Method Overview Surfacing Coordination in 2019 UK GE Analysis of Coordinated Behaviors 摘要&#xff1a; 协调的在线行为是信息…