当前位置: 首页 > news >正文

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功能流程图:

在这里插入图片描述

相关文章:

Flutter视图原理之StatefulWidget,InheritedWidget

目录 StatefulElement1. 构造函数2. build3. _firstBuild3. didChangeDependencies4. setState InheritedElement1. Element类2. _updateInheritance3. InheritedWidget数据向下传递3.1 dependOnInheritedWidgetOfExactType 4. InheritedWidget的状态绑定4.1. ProxyElement 在f…...

观察者模式-对象间的联动

有个商城小程序&#xff0c;用户希望当有新品上市的时候能通知他们。这样用户就可以不要时刻盯着小程序了。在这个场景中&#xff0c;用户向小程序订阅了一个服务——发送新品短信。小程序在有新品上线时负责向订阅客户发出这个消息。 这就是发布-订阅模式&#xff0c;也称观察…...

Webpack十大缺点:当过度工程化遇上简单的静态页面

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…...

新手指南|如何快速参与Moonbeam Ignite

Moonbeam Ignite是社区熟悉的有奖链上交互活动&#xff0c;将有300万枚GLMR作为生态激励注入Moonbeam生态系统&#xff0c;体验MoonbeamMoonbeam生态的应用即可获取相应Token奖励。Beamex/Beamswap、Moonwell和Gamma作为首批参与Moonbeam Ignite的三家项目方&#xff0c;将在活…...

VR航天科普主题公园模拟太空舱体验馆vr航天模拟体验设备

VR航天航空体验馆巡展是一项非常受欢迎的展览活动&#xff0c;可以让公众在现场体验到航天飞行的乐趣。 普乐蛙VR展览组织者会设计一个航天航空主题的VR体验馆&#xff0c;并在馆内设置各种航天航空相关的展示内容&#xff0c;如太空舱、火箭发射、星际航行等。 其次&#xff0…...

Spring Boot OAuth 2.0整合详解

目录 一、Spring Boot 2.x 示例 1、初始化设置 2、设置重定向URI 3、配置 application.yml 4、启动应用程序 二、Spring Boot 2.x 属性映射 二、CommonOAuth2Provider 三、配置自定义提供者&#xff08;Provider&#xff09;属性 四、覆盖 Spring Boot 2.x 的自动配置…...

安装visual studio报错“无法安装msodbcsql“

在安装visual studio2022时安装完成后提示无法安装msodbcsql, 查看日志文件详细信息提示&#xff1a;指定账户已存在。 未能安装包“msodbcsql,version17.2.30929.1,chipx64,languagezh-CN”。 搜索 URL https://aka.ms/VSSetupErrorReports?qPackageIdmsodbcsql;PackageActi…...

webGL编程指南 第三章 矩阵平移三角形.translatedTriangle_Matrix

我会持续更新关于wegl的编程指南中的代码。 当前的代码不会使用书中的缩写&#xff0c;每一步都是会展开写。希望能给后来学习的一些帮助 git代码地址 &#xff1a;git 接着 上一节 中 我们使用矩阵进行旋转&#xff0c;这次我们使用矩阵实现位移 <!DOCTYPE html> <…...

修改echarts的tooltip样式 折线图如何配置阴影并实现渐变色和自适应

图片展示 一、引入echarts 这里不用多解释 vue里使用 import echarts from “echarts”; html页面引用js文件或用script标签引用 二、定义一个具有宽高的dom div <div id"echart-broken" style"width:400px;height: 200px;"></div>三、定义…...

[论文笔记] SurroundOcc: Multi-Camera 3D Occupancy Prediction for Autonomous Driving

Wei, Yi, et al. “Surroundocc: Multi-camera 3d occupancy prediction for autonomous driving.” Proceedings of the IEEE/CVF International Conference on Computer Vision. 2023. 重点记录 将占用网格应用到多个相机构成的3D空间中; 使用BEVFormer中的方法获取3D特征, …...

辅助驾驶功能开发-功能对标篇(16)-NOA 城市辅助系统-毫末智行

1.横向对标参数 厂商毫末智行车型魏牌摩卡DHT-PHEV上市时间发布:2022年8月30日 上市:2022年底前方案12V5R2L+1DMS摄像头前视摄像头*3【800W】侧视摄像头*4后视摄像头*1【800W】环视摄像头*4DMS摄像头*1雷达毫米波雷达*54D毫米波雷达/超声波雷达*12激光雷达*2【速腾聚创 M1,1…...

H3C的IRF堆叠互联关系说明

H3C IRF堆叠互联说明48口交换机连接方式IRF Port 两台设备第一台的51口 第二台的51口irf-port 1/2 port group interface ten-gigabitethernet 1/0/51 port group interface ten-gigabitethernet 1/0/52第一台的52口第二台的52口irf-port 2/1 port group interface ten-gigabi…...

货物摆放(蓝桥杯)

货物摆放 题目描述 小蓝有一个超大的仓库&#xff0c;可以摆放很多货物。 现在&#xff0c;小蓝有 n 箱货物要摆放在仓库&#xff0c;每箱货物都是规则的正方体。小蓝规定了长、宽、高三个互相垂直的方向&#xff0c;每箱货物的边都必须严格平行于长、宽、高。 小蓝希望所有的…...

3782: 【C3】【穷举】弹珠游戏

目录 题目描述 输入 输出 样例输入 样例输出 题目描述 游戏的内容是&#xff1a;在一个 n*n 的矩阵里&#xff0c;有若干个敌人&#xff0c;你的弹珠可以摧毁敌人&#xff0c;但只能攻击你所在的行、列里的所有敌人&#xff0c;然后你就可以获得他们的分数之和&#xff0…...

leetcode 5

leetcode 5 题目是通过枚举字符串&#xff0c;然后判断是否子字符串满足回文。 引用传递和值传递相比&#xff0c;引用传递可以减少内存空间。提高代码运行效率。 https://www.cnblogs.com/yanlingyin/archive/2011/12/07/2278961.html...

centos中nacos设置开机自启动

以下实践亲测有效&#xff01; 1、在以下目录编辑新建nacos.service文件 vim /lib/systemd/system/nacos.service [Unit] Descriptionnacos Afternetwork.target [Service] Typeforking ExecStart/usr/local/nacos/bin/startup.sh -m standalone ExecReload/usr/local/nacos/b…...

双指针——移动零

一&#xff0c;题目要求&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0…...

WPF中在MVVM模式下实现导航功能

WPF中在MVVM模式下实现导航功能 一、利用TabControl 使用场景&#xff1a;项目小&#xff0c;不用考虑内存开销的问题。 实现方式1-手动指定ViewModel 分别定义3个UserControl作为View用于演示 <UserControl...><Grid><StackPanel Orientation"Vertic…...

SpringBoot面试题2:SpringBoot与SpringCloud 区别?SpringBoot和Spring、SpringMVC的区别

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:SpringBoot与SpringCloud 区别? Spring Boot 和 Spring Cloud 是 Spring 生态系统中的两个关键组件,它们有以下区别: 定位:Spring Boot 用于简…...

Practical Deep Raw Image Denoisingon Mobile Devices

Abstract 近年来&#xff0c;基于深度学习的图像去噪方法得到了广泛的研究&#xff0c;并在许多公共基准数据集中盛行。然而&#xff0c;最先进的网络计算成本太高&#xff0c;无法直接应用于移动设备。在这项工作中&#xff0c;我们提出了一种轻量级、高效的基于神经网络的原…...

如何在Android项目中制作和使用三方包(jar文件)

文章目录 1 概念介绍2 制作方法2.1 制作步骤2.2 制作结果3 使用方法3.1 具体步骤3.2 示例代码4 内容总结在项目中为了跨部门协作需要把相关的内容打成包文件,基于这个需求,我们将介绍如何把 代码制作成三方包,这里的三方包是指jar文件。同时也会介绍如何在Android项目中使用…...

消息队列Beanstalkd介绍

摘要&#xff1a; Beanstalkd是一个高性能、轻量级的、分布式的、内存型的消息队列系统。最初设计的目的是想通过后台异步执行耗时的任务来降低高容量Web应用系统的页面访问延迟。其实Beanstalkd是典型的类Memcached设计&#xff0c;协议和使用方式都是同样的风格。其基本设计思…...

【C++】继承 ⑥ ( 继承中的构造函数和析构函数 | 类型兼容性原则 | 父类指针 指向 子类对象 | 使用 子类对象 为 父类对象 进行初始化 )

文章目录 一、public 公有继承 - 示例分析1、类型兼容性原则2、类型兼容性原则应用场景 二、类型兼容性原则 - 示例分析1、父类指针 指向 子类对象2、使用 子类对象 为 父类对象 进行初始化3、完整代码示例 一、public 公有继承 - 示例分析 1、类型兼容性原则 类型兼容性原则 :…...

15 | JPA 对 Web MVC 开发者做了哪些支持

我们使用 Spring Data JPA 的时候&#xff0c;一般都会用到 Spring MVC&#xff0c;Spring Data 对 Spring MVC 做了很好的支持&#xff0c;体现在以下几个方面&#xff1a; 支持在 Controller 层直接返回实体&#xff0c;而不使用其显式的调用方法&#xff1b;对 MVC 层支持标…...

链表的概念+MySingleList的实现

文章目录 链表一、 链表的概念1.概念2. 结构 二、MySingleList的实现1 .定义内部类2 .创建链表3. 遍历链表并打印4.查找单链表中是否包含关键字key5.得到链表的长度6.头插法7. 尾插法8.任意位置插入8.删除结点清空 链表 顺序存储&#xff1a;顺序表/ArrayList 优点&#xff1…...

小黑子—Maven基础

Maven基础 一 小黑子的Maven学习1. Mavn的介绍2. Maven基础概念2.1 仓库2.2 坐标2.3 仓库配置 3. 手动写一个maven项目3.1 Maven项目构建命令3.2 插件创建工程 4. IDEA下的maven项目5. 依赖管理5.1 依赖配置5.2 依赖传递5.3 可选依赖&#xff08;不透明&#xff09;5.4 排除依赖…...

【Netty专题】【网络编程】从OSI、TCP/IP网络模型开始到BIO、NIO(Netty前置知识)

目录 前言前置知识一、计算机网络体系结构二、TCP/IP协议族2.1 简介*2.2 TCP/IP网络传输中的数据2.3 地址和端口号2.4 小总结 三、TCP/UDP特性3.1 TCP特性TCP 3次握手TCP 4次挥手TCP头部结构体 3.2 UDP特性 四、总结 课程内容一、网络通信编程基础知识1.1 什么是Socket1.2 长连…...

扬帆起航:许战海方法论日文版正式发布

近日&#xff0c;中国头部战略咨询机构‘许战海咨询’最新研究成果《中国汽车行业新能源转型战略》行业白皮书日文版&#xff0c;即将在日本发布。同时发布的日文版核心方法论白皮书还有《主品牌进化战略》、《第二招牌增长战略》、《链主品牌&#xff1a;制造业的竞争之王》等…...

Docker 安装zookeeper

一、安装单机版 1、拉取镜像 docker pull zookeeper2、创建挂载目录 mkdir -p /mydata/zookeeper/{conf,data,logs}3、新建配置文件 cd /mydata/zookeeper/conf vi zoo.cfgdataDir/data dataLogDir/logs tickTime2000 initLimit10 syncLimit5 clientPort21814、单机主机启…...

项目管理与SSM框架(二)| Spring

Spring简介 Spring是一个开源框架&#xff0c;为简化企业级开发而生。它以IOC&#xff08;控制反转&#xff09;和AOP&#xff08;面向切面&#xff09;为思想内核&#xff0c;提供了控制层 SpringMVC、数据层SpringData、服务层事务管理等众多技术&#xff0c;并可以整合众多…...