Flutter+【三棵树】

news/2024/4/26 10:22:10/文章来源:https://blog.csdn.net/Androidxiaofei/article/details/129184466

定义

在Flutter中和Widgets一起协同工作的还有另外两个伙伴:Elements和RenderObjects;由于它们都是有着树形结构,所以经常会称它们为三棵树。

这三棵树分别是:Widget、Element、RenderObject

  • Widget树:寄存烘托内容、视图布局信息;
  • Element树:根据 Widget 的布局特点进行 layout 和制作;
  • RenderObject树:寄存上下文,经过 Element 遍历视图树,Element 一起持有 Widget 和RenderObject;

三棵树原理

Widget 树

Widget 是用户页面的描述,表明晰 Element 的配置信息,Flutter 页面都是由各式各样的 Widget 组合声明成的。Widget本身是不可变的 immutable,

这也便是说,一切它直接声明或承继的变量都必须为 final 类型的。如果想给 widget 相关一个可变的状况,就要考虑运用 StatefulWidget ,它会经过 createState 创立一个State目标,然后每当它转化成一个 Element 时会合并到树上。

而对于 Widget 又分为无状况的 StatelessWidget 和有状况的 StatefullWidget

  • StatelessWidget:无中心状况改动的widget,需求更新展现内容就得经过从头创立,flutter推荐尽量运用StatelessWidget;
  • StatefullWidget:存在中心状况改动,那么问题来了,widget不是都immutable的,状况改动存储在哪里?flutter 引进state的类用于寄存中心态,经过调用state.setState()进行此节点及以下的整个子树更新;

State

一个StatefulWidget类会对应一个State类,State表明与其对应的StatefulWidget要保护的状况,State中的保存的状况信息可以:

在 Widget 构建时可以被同步读取。 在 Widget 生命周期中可以被改动,当State被改动时,可以手动调用其setState()办法通知Flutter framework状况发生改动,Flutter framework 在收到音讯后,会从头调用其build办法从头构建Widget树,然后到达更新UI的意图。

对于Widget的生命周期,这篇文章有介绍:juejin.cn/post/703469…

Element 树

Widget 树是十分不稳定的,经常会履行 build 办法,一旦调用 build 办法意味着这个 Widget 依靠的一切其他 Widget 都会从头创立,如果 Flutter 直接解析 Widget树,将其转化为 RenderObject 树来直接进行烘托,那么将会是一个十分消耗功能的进程,那对应的肯定有一个东西来消化这些改动中的不方便,来做cache。

所以,这儿就有另外一棵树 Element 树。Element 树这一层将 Widget 树的改动做了抽象,可以只将真正需求修正的部分同步到实在的 RenderObject 树中,最大程度下降对实在烘托视图的修正,提高烘托效率,而不是毁掉整个烘托视图树重建。

RenderObject 树

烘托树的使命便是做组件的详细的布局烘托作业,烘托树上每个节点都是一个承继自 RenderObject 类的目标,在 Element 中有 createRenderObject 生成RenderObject,该目标内部提供多个特点及办法来协助框架层中的组件如何布局烘托。RenderObject 用于使用界面的布局和制作,保存了元素的巨细,布局等信息,实例化一个 RenderObject 是十分耗费功能的。

初次运行三棵树

初步认识了三棵树之后,那Flutter是如何创建布局的?以及三棵树之间他们是如何协同的呢?接下来就让我们通过一个简单的例子来剖析下它们内在的协同关系:

class ThreeTree extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(color: Colors.red,child: Container(color: Colors.blue));}
}

上面这个例子很简单,它由三个Widget组成:ThreeTree、Container、Text。那么当Flutter的runApp()方法被调用时会发生什么呢?

当runApp()被调用时,第一时间会在后台发生以下事件:

  • Flutter会构建包含这三个Widget的Widgets树;
  • Flutter遍历Widget树,然后根据其中的Widget调用createElement()来创建相应的Element对象,最后将这些对象组建成Element树;
  • 接下来会创建第三个树,这个树中包含了与Widget对应的Element通过createRenderObject()创建的RenderObject;

下图是Flutter经过这三个步骤后的状态:

从图中可以看出Flutter创建了三个不同的树,一个对应着Widget,一个对应着Element,一个对应着RenderObject。每一个Element中都有着相对应的Widget和RenderObject的引用。可以说Element是存在于可变Widget树和不可变RenderObject树之间的桥梁。Element擅长比较两个Object,在Flutter里面就是Widget和RenderObject。它的作用是配置好Widget在树中的位置,并且保持对于相对应的RenderObject和Widget的引用。

三棵树的作用

简而言之是为了性能,为了复用Element从而减少频繁创建和销毁RenderObject。因为实例化一个RenderObject的成本是很高的,频繁的实例化和销毁RenderObject对性能的影响比较大,所以当Widget树改变的时候,Flutter使用Element树来比较新的Widget树和原来的Widget树:

//framework.dart@protectedElement updateChild(Element child, Widget newWidget, dynamic newSlot) {if (newWidget == null) {if (child != null)deactivateChild(child);return null;}Element newChild;if (child != null) {assert(() {final int oldElementClass = Element._debugConcreteSubtype(child);final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);hasSameSuperclass = oldElementClass == newWidgetClass;return true;}());if (hasSameSuperclass && child.widget == newWidget) {if (child.slot != newSlot)updateSlotForChild(child, newSlot);newChild = child;} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {if (child.slot != newSlot)updateSlotForChild(child, newSlot);child.update(newWidget);assert(child.widget == newWidget);assert(() {child.owner._debugElementWasRebuilt(child);return true;}());newChild = child;} else {deactivateChild(child);assert(child._parent == null);newChild = inflateWidget(newWidget, newSlot);}} else {newChild = inflateWidget(newWidget, newSlot);}
​assert(() {if (child != null)_debugRemoveGlobalKeyReservation(child);final Key key = newWidget?.key;if (key is GlobalKey) {key._debugReserveFor(this, newChild);}return true;}());return newChild;}
...static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType&& oldWidget.key == newWidget.key;}
  • 如果某一个位置的Widget和新Widget不一致,才需要重新创建Element;
  • 如果某一个位置的Widget和新Widget一致时(两个widget相等或runtimeType与key相等),则只需要修改RenderObject的配置,不用进行耗费性能的RenderObject的实例化工作了;
  1. 因为Widget是非常轻量级的,实例化耗费的性能很少,所以它是描述APP的状态(也就是configuration)的最好工具;
  2. 重量级的RenderObject(创建十分耗费性能)则需要尽可能少的创建,并尽可能的复用;

看到这里你是否会觉得整个Flutter APP就像是一个RecycleView呢?

因为在框架中,Element是被抽离开来的,所以你不需要经常和它们打交道。每个Widget的build(BuildContext context)方法中传递的context就是实现了BuildContext接口的Element。

更新时的三棵树

因为Widget是不可变的,当某个Widget的配置改变的时候,整个Widget树都需要被重建。例如当我们改变一个Container的颜色为橙色的时候,框架就会触发一个重建整个Widget树的动作。因为有了Element的存在,Flutter会比较新的Widget树中的第一个Widget和之前的Widget。接下来比较Widget树中第二个Widget和之前Widget,以此类推,直到Widget树比较完成。

class ThreeTree extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(color: Colors.orange,child: Container(color: Colors.blue,),);}
}

Flutter遵循一个最基本的原则:判断新的Widget和老的Widget是否是同一个类型:

  • 如果不是同一个类型,那就把Widget、Element、RenderObject分别从它们的树(包括它们的子树)上移除,然后创建新的对象;
  • 如果是一个类型,那就仅仅修改RenderObject中的配置,然后继续向下遍历;

在我们的例子中,ThreeTree Widget是和原来一样的类型,它的配置也是和原来的ThreeTreeRender一样的,所以什么都不会发生。下一个节点在Widget树中是Container Widget,它的类型和原来是一样的,但是它的颜色变化了,所以RenderObject的配置也会发生对应的变化,然后它会重新渲染,其他的对象都保持不变。

注意这三个树,配置发生改变之后,Element和RenderObject实例没有发生变化。

上面这个过程是非常快的,因为Widget的不变性和轻量级使得他能快速的创建,这个过程中那些重量级的RenderObject则是保持不变的,直到与其相对应类型的Widget从Widget树中被移除。

当Widget的类型发生改变时

class ThreeTree extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(color: Colors.orange,child: FlatButton(onPressed: () {},child: Text('三棵树'),),);}
}

和刚才流程一样,Flutter会从新Widget树的顶端向下遍历,与原有树中的Widget类型进行对比。

因为FlatButton的类型与Element树中相对应位置的Element的类型不同,Flutter将会从各自的树上删除这个Element和相对应的ContainerRender,然后Flutter将会重建与FlatButton相对应的Element和RenderObject。

全文到这大致为他的原理及代码解析;有关更多的flutter的高级进阶知识,大家可以参考《Flutter混合开发手册》这个技术文档,里面包含flutter的全部技术覆盖。

Flutter三棵树联系

首先要知道,启动App整个创立树的流程是什么:

  • 创立 widget 树
  • 调用 runApp(rootWidget),将 rootWidget 传给 rootElement ,做为 rootElement 的子节点,生成 Element 树,再由 Element 树生成 Render 树

  • Widget:寄存烘托内容、视图布局信息,widget的特点最好都是immutable
  • Element:寄存上下文,经过Element遍历视图树,Element一起持有Widget和RenderObject
  • RenderObject:根据Widget的布局特点进行layout,paint Widget传人的内容

从创立到烘托的大体流程是:

  • 根据 Widget生成 Element,然后创立相应的RenderObject并相关到Element.renderObject特点上,最终再经过RenderObject来完结布局摆放和制作。
  • Element便是Widget在UI树详细位置的一个实例化目标,大多数Element只要仅有的renderObject,但还有一些Element会有多个子节点,如承继自RenderObjectElement的一些类,比如MultiChildRenderObjectElement。
  • 最终一切Element的RenderObject构成一棵树,咱们称之为Render Tree即烘托树。

总结一下,咱们可以以为Flutter的UI系统包含三棵树:Widget树、Element树、RenderObject树。他们的依靠联系是:Element树根据Widget树生成,而烘托树又依靠于Element树,最终的UI树其实是由一个个独立的Element节点构成。

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

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

相关文章

SigmaPlot科学绘图工具:ROC曲线分析及AUC组间差异的显著性分析

目的 初步使用SigmaPlot科学绘图工具;进行ROC曲线绘制并分析检验变量AUC组间差异性是否显著 软件下载及安装 SigmaPlot下载安装按照这个教程即可:https://www.hhkxxw.com/24799.html 快速通道:SigmaPlot下载链接:百度网盘链接…

DC220V冲击继电器RCJ-3

系列型号 RCJ-2型冲击继电器; RCJ-2/48VDC冲击继电器 RCJ-2/110VDC冲击继电器 RCJ-2/220VDC冲击继电器 RCJ-2/100VAC冲击继电器 RCJ-2/127VAC冲击继电器 RCJ-2/220VAC冲击继电器 RCJ-3/220VAC冲击继电器 RCJ-3型冲击继电器 RCJ-3/127VAC冲击继电器 RCJ-3/100VAC冲…

FastCGI sent in stderr: "PHP message: PHP Fatal error

服务器php7.2卸载安装7.4之后,打开网站一直无法访问,查看nginx错误日志发现一直报这个错误:2023/02/23 11:12:55 [error] 4735#0: *21 FastCGI sent in stderr: "PHP message: PHP Fatal error: Uncaught ReflectionException: Class translator does not exist in …

Python四大主题之一【 Web】 编程框架

目前Python的网络编程框架已经多达几十个,逐个学习它们显然不现实。但这些框架在系统架构和运行环境中有很多共通之处,本文带领读者学习基于Python网络框架开发的常用知识,及目前的4种主流Python网络框架:Django、Tornado、Flask、Twisted。 …

100%BIM学员的疑惑:不会CAD可以学Revit吗?

在新一轮科技创新和产业变革中,信息化与建筑业的融合发展已成为建筑业发展的方向,将对建筑业发展带来战略性和全局性的影响。 建筑业是传统产业,推动建筑业科技创新,加快推进信息化发展,激发创新活力,培育…

web客户端-websocket

1、websocket简介 WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。 WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手&#xff0c…

python3.11.2安装 + pycharm安装

下载 :https://www.python.org/ 2.双击下载的软件: 3.进入安装界面 下一步,点击 是 上一步点击后就看到如下: 安装成功了,接下来检测一下:cmd 安装pycharm PyCharm是一种Python IDE(Integr…

Apifox-比postman更优秀的接口自动化测试平台

一、Apifox介绍 Apifox 是 API 文档、API 调试、API Mock、API 自动化测试一体化协作平台,定位 Postman Swagger Mock JMeter。通过一套系统、一份数据,解决多个系统之间的数据同步问题。只要定义好 API 文档,API 调试、API 数据 Mock、A…

你真的需要文档管理软件吗?

什么是文档管理软件? 文档管理软件 (DMS) 是一种数字解决方案,可帮助组织处理、捕获、存储、管理和跟踪文档。 通过严格管理您的关键业务信息,您可以开发以稳定、可预测、可衡量的方式启动、执行和完成的流程。 如果没有功能齐全的文档管理软…

从事Python自动化测试,30岁熬到月薪20K+,分享我的多年面试经…

年少不懂面试经,读懂已是测试人。 大家好,我是小码哥,一名历经沧桑,看透互联网行业百态的测试从业者,经过数年的勤学苦练,精钻深研究,终于从初出茅庐的职场新手成长为现在的测试老鸟&#xff0…

zabbix4.0安装部署

目录 1.1、添加 Zabbix 软件仓库 1.2、安装 Server/proxy/前端 1.3、创建数据库 1.4、导入数据 1.5、为 Zabbix server/proxy 配置数据库 1.6、 启动 Zabbix server 进程 1.7、zabbix前端配置 SELinux 配置 1.8、安装 Agent 1.9、启动zabbix 2.0、访问zabbix 1.1、添加…

ThinkPHP ^6图片操作进阶

图片裁剪、缩略、水印不再是TP框架系统内置的功能,需要安装。 目录 安装 图片处理 1.创建图片对象 2.获取图片属性 3.裁剪图像 4.生成缩略图 6.保存图像 7.水印 安装 使用composer在项目根目录打开命令行执行: composer require topthink/think…

mybatis-plus分页方式

拦截器(分页插件) 一 方式1:XxxMapper.selectPage 1 selectPage(page, null) 概述 MyBatisPlus中提供的(自带的)分页插件,非常简单,只需要简单的配置就可以实现分页功能。详细步骤: 第一步:&…

【Tips】通过背数据了解业务

学习资料:做了三年数据分析,给你的几点建议 1. 通过背数据了解业务 原文: 总结: 方法:每天早上去到公司第一件事情就是先背一遍最新的各种指标。原理: 数据敏感性就是建立在对数据的了解和熟悉上。业务的…

一文带你了解排序算法

排序算法平均时间复杂度冒泡排序O(n2)选择排序O(n2)插入排序O(n2)希尔排序O(n1.5)快速排序O(N*logN)归并排序O(N*logN)堆排序O(N*logN)基数排序O(d(nr)) 一. 冒泡排序(BubbleSort) 1、基本思想:两个数比较大小,较大的数下沉,较小的数冒起来。…

Spring boot开启定时任务的三种方式(内含源代码+sql文件)

Spring boot开启定时任务的三种方式(内含源代码sql文件) 源代码sql文件下载链接地址:https://download.csdn.net/download/weixin_46411355/87486580 目录Spring boot开启定时任务的三种方式(内含源代码sql文件)源代码…

FreeRTOS入门(02):任务基础使用与说明

文章目录目的创建任务任务调度任务控制延时函数任务句柄获取与修改任务优先级删除任务挂起与恢复任务强制任务离开阻塞状态空闲任务总结目的 任务(Task)是FreeRTOS中供用户使用的最核心的功能,本文将介绍任务创建与使用相关的基础内容。 本…

Tina_Linux_启动优化_开发指南

文章目录Tina_Linux_启动优化_开发指南1 概述2 启动速度优化简介2.1 启动流程2.2 测量方法2.2.1 printk time2.2.2 initcall_debug2.2.3 bootgraph.2.2.4 bootchart2.2.5 gpio 示波器.2.2.6 grabserial.2.3 优化方法2.3.1 boot0启动优化2.3.1.1 非安全启动.2.3.1.2 安全启动2.3…

关于selenium的等待

目录 隐式等待 显式等待 注意事项 隐式等待 简单来说:在规定的时间范围内,轮询等待元素出现之后就立即结束。 如果在规定的时间范围内,元素仍然没有出现,则会抛出一个异常【NoSuchElementException】,脚本停止运行…

2023上半年软考各省份报名时间已公布!

截止至目前,共有2个省市公布了2023上半年软考报名时间,一起来看看吧~ 一、2023上半年软考考试事项 分数线:所有科目成绩全部在45分以上(含45分)通过考试; 出成绩时间:预计在7月中旬&#xff1…