Flutter视图原理之StatefulWidget,InheritedWidget

news2024/12/28 8:03:41

目录

    • 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 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);
    }
  }
  1. 先判断updateShouldNotify接口需要根据widget来判断,是否通知依赖集合
  2. 如果需要通知,那么遍历_dependents集合,首先验证是否是自己的子孙,接着验证子element._dependencies集合是否有parent;
  3. 接着通知依赖的子项,调用dependent.didChangeDependencies();

element实现:

  void didChangeDependencies() {
    assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
    assert(_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.coloradmin.cn/o/1110390.html

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

相关文章

为中小企业的网络推广策略解析:扩大品牌知名度和曝光度

目前网络推广已经成为企业获取潜在客户和提升品牌知名度的重要手段。对于中小企业而言&#xff0c;网络推广是一个具有巨大潜力和可行性的营销策略。在本文中&#xff0c;我们将探讨中小企业为什么有必要进行网络推广&#xff0c;并分享一些实用的网络推广策略。 一、扩大品牌知…

【Go之道】探索Go语言之旅:基础与进阶指南

在这个数字化快速发展的时代&#xff0c;掌握一门编程语言已成为必备技能。今天&#xff0c;我将带你踏上【Go之道】&#xff0c;探索Go语言的魅力&#xff0c;为你的编程之旅助力。 一、Go语言概述 Go&#xff0c;又称为Golang&#xff0c;是由Google设计和开发的一种静态类型…

如何提高嵌入式软件工程师的技术深度?

今日话题&#xff0c;如何提高嵌入式软件工程师的技术深度&#xff1f;建立坚实的基础知识是深入研究的关键。只有深入理解基础知识&#xff0c;才能在理论指导下不断深化和扩展自己的技术。没有坚实的基础&#xff0c;深入研究就显得空中楼阁。如果你有兴趣进入嵌入式行业我可…

WebDAV之π-Disk派盘 + Autosync

Autosync如何设置可以让各种云储存软件能够自动的同步备份手机上面的各种内容,让你手机当中的重要文件内容能够得到保存,让你的重要文件不会在手机上面丢失,随时都能够在各种云储存当中找到?快来试下Autosync自动同步工具吧。 Autosync是一款自动文件同步和备份工具。 它能…

StyleCLIP global direction详解

StyleCLIP中global direction的实现原理 前言第一阶段:预计算生成latents计算均值与标准差计算 Δ i \Delta i Δi 第二阶段:计算与文本的对应关系 前言 基于的假设&#xff1a; CLIP中虽然图像特征与文本特征不存在一一对应的关系&#xff0c;但相同的语义下&#xff0c;图像…

iOS上架App Store的全攻略

​ 第一步&#xff1a;申请开发者账号 在开始将应用上架到App Store之前&#xff0c;你需要申请一个开发者账号。 1.1 打开苹果开发者中心网站&#xff1a;Apple Developer 1.2 使用Apple ID和密码登录&#xff08;如果没有账号则需要注册&#xff09;&#xff0c;要确保使用…

会议OA项目-其它页面->自定义组件应用,其它界面的布局

1.自定义组件应用 文档参考:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/ //oamin\project.config.json {"description": "项目配置文件","packOptions": {"ignore": [],"include": []},…

聊聊BOM的基础概念、管理难点

物料清单&#xff08;Bill of Materials&#xff0c;简称BOM&#xff09;是描述产品组成结构的信息数据。BOM信息是制造信息化/数字化管理的最核心的基础数据&#xff0c;BOM信息贯穿从产品设计、生产计划制定、物料采购和销售服务等制造全业务流程&#xff0c;是开展生产活动的…

vue 插槽 - 具名插槽

vue 插槽 - 具名插槽 **创建 工程&#xff1a; H:\java_work\java_springboot\vue_study ctrl按住不放 右键 悬着 powershell H:\java_work\java_springboot\js_study\Vue2_3入门到实战-配套资料\01-随堂代码素材\day05\准备代码\09-插槽-具名插槽 vue --version vue create…

兼容支付宝抖音小程序的工具还能把他们迁移到自己的app

事情的起因是这样的。 之前在微信、支付宝和抖音开放平台都上架了自己的小程序&#xff0c;虽然几个平台有自己的开发标准&#xff0c;但是都是基于 JavaScript 这种已经被广泛使用的编程语言进行开发的&#xff0c;对于开发者而言学习的门槛并不高&#xff0c;也很容易进行开…

标准的Gabor滤波器及Log_Gabor滤波器的实现、解析、速度优化及其和Halcon中gen_gabor的比较。

最近有朋友在研究Halcon中gen_gabor的函数&#xff0c;和我探讨&#xff0c;因为我之前也没有怎么去关注这个函数&#xff0c;因此&#xff0c;前前后后大概也折腾了有一个星期去模拟实现这个东西&#xff0c;虽然最终没有实现这个函数&#xff0c;但是也是有所收获&#xff0c…

协程,GIL全局解释器,互斥锁,线程池,Concurrent模块

进程是资源分配的最小单位&#xff0c;线程是CPU调度的最小单位。每一个进程中至少有一个线程。 Python对并发编程的支持 (1)多线程&#xff1a;threading&#xff0c;利用CPU和IO可以同时执行的原理&#xff0c;让CPU不会干巴巴等待IO完成。 (2)多进程&#xff1a;multiproces…

nvcc -V和nvidia-smi的关系

nvcc -V 和 nvidia-smi 都与NVIDIA GPU相关&#xff0c;但它们提供的信息和功能有所不同。 nvcc -V: nvcc 是 NVIDIA CUDA 编译器的命令&#xff0c;用于获取CUDA工具包的版本信息。CUDA&#xff08;Compute Unified Device Architecture&#xff09;是一种用于并行计算的GPU编…

阿里云短信服务

文章目录 了解阿里云用户权限操作开通阿里云短信服务添加短信模板添加签名编写测试代码编写可重复的微服务接口&#xff0c;实现验证码的发送&#xff01; 了解阿里云用户权限操作 模型 去阿里云个人中心查看授权码等&#xff1a; 点击开始使用用户的AccessKey 创建用户组&…

用户登录管理中的Bug修复与技术思考

目录 1 前言2 问题提出3 问题分析和解决4 技术分析和改进5 结语 1 前言 在开发管理软件平台为美术馆时&#xff0c;我们致力于提供一个多系统集成平台&#xff0c;其中包括艺术品管理、志愿者管理和数字资产管理等子系统。为了确保用户享有流畅的体验&#xff0c;我们采用了一…

面向对象设计原则之单一职责原则

目录 定义作用及影响示例 面向对象设计原则之开-闭原则 面向对象设计原则之里式替换原则 面向对象设计原则之依赖倒置原则 面向对象设计原则之单一职责原则 定义 单一职责原则 / 单一功能原则 &#xff08;Single Responsibility Principle&#xff0c;SRP&#xff09;&#x…

实验2.2.1 交换机VLAN的划分

实验2.2.1 交换机VLAN的划分 一、任务描述二、任务分析三、实验拓扑四、具体要求五、任务实施1.重命名交换机&#xff0c;关闭干扰信息&#xff0c;并创建vlan。2.通过display vlan查看vlan相关信息3.配置Access接口及分配vlan接口。4.查看vlan的相关信息。 六、任务验收七、任…

【PXIE301-211】青翼科技基于PXIE总线的16路并行LVDS数据采集、1路光纤数据收发处理平台

板卡概述 PXIE301-211是一款基于PXIE总线架构的16路并行LVDS数据采集、1路光纤收发处理平台&#xff0c;该板卡采用Xilinx的高性能Kintex 7系列FPGA XC7K325T作为实时处理器&#xff0c;实现各个接口之间的互联。板载1组64位的DDR3 SDRAM用作数据缓存。板卡具有1个FMC&#xf…

婚纱摄影行业如何利用软文精准获客

婚纱摄影在整个结婚流程中处于中上游&#xff0c;因此婚摄环节是整个婚庆的重要环节&#xff0c;市场的强烈需求也使整个行业的规模不断扩张&#xff0c;那么在激烈的市场竞争中&#xff0c;婚纱摄影行业应该如何获得源源不断的客户呢&#xff0c;可以试试软文&#xff0c;接下…