Flutter+【三棵树】
定义
在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的实例化工作了;
- 因为Widget是非常轻量级的,实例化耗费的性能很少,所以它是描述APP的状态(也就是configuration)的最好工具;
- 重量级的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节点构成。
相关文章:

Flutter+【三棵树】
定义 在Flutter中和Widgets一起协同工作的还有另外两个伙伴:Elements和RenderObjects;由于它们都是有着树形结构,所以经常会称它们为三棵树。 这三棵树分别是:Widget、Element、RenderObject Widget树:寄存烘托内容…...

若依系统【SpringBoot】如何集成qq邮件发送【超详细,建议收藏】
若依系统的部署博主就不在这儿阐述了,默认大家的电脑已经部署好了若依系统,这里直接开始集成邮件系统,首先我们得需要对qq邮箱进行配置;一套学不会你来打我😀; 一、开启我们的qq邮箱发送邮件的配置 1、先进…...

kettle使用--1.mysql多表关联导入mongoDB
文章目录1. 初步体验:csv 转为excelKettle概念配置mysql链接mysql 一对多关联查询结果保存到mongodb中1. 初步体验:csv 转为excel Windows环境下安装pdi-ce-8.0.0.0-28.zip ,解压后执行lib下的Spoon.bat 将csv输入拖入 双击拖进去的csv&…...
2023年CDGA考试-第10章-参考数据和主数据(含答案)
2023年CDGA考试-第10章-参考数据和主数据(含答案) 单选题 1.实现主数据中心环境的三种基本方法中不包括哪种? A.参考目录 B.注册表 C.交易中心 D.混合模式 答案 A 2.参考数据还具有很多区别于其他主数据 (例如,企业结构数据和交易结构数据)的特征。以下哪项目描述错误的…...

2023年,什么行业更有发展前景?
关于有前景有发展的行业推荐,小课今天还是推荐咱们IT互联网行业。 很多人会说现在懂电脑的那么多,这个行业都饱和了,很多学电脑的找不到工作都改行了。但事实是现在每个行各业都需要互联网,需要懂电脑的技术人才,尤其是在云计算、大数据到来…...

致盛咨询携手亚马逊云科技进一步开拓中国市场
作为医疗保健领域的咨询公司,ZS需要保证服务可靠性、敏捷性和安全性的同时,获得经济效益。亚马逊云科技丰富的云服务产品简化了ZS基础架构的搭建,为ZS节省了大量的人力与资金成本。同时,缩短了ZS扩展基础设施的周转时间࿰…...
ts之 命名空间 namespace、三斜线指令、声明文件(declare 声明ts的变量函数第三方模块等 )
目录ts之 命名空间 namespacets之 命名空间 namespacets之 三斜线指令 ( 引入其他.ts文件 )app.tsindex.tsts之 声明文件 d.ts - declare01:declare声明express第三方模块typings 为代码或者第三方模块 编写声明文件index.ts02:de…...

Day898.Join语句执行流程 -MySQL实战
Join语句执行流程 Hi,我是阿昌,今天学习记录的是关于Join语句执行流程的内容。 在实际生产中,关于 join 语句使用的问题,一般会集中在以下两类: 不让使用 join,使用 join 有什么问题呢?如果有…...

ChatGPT商业前景如何?人工智能未来会如何发展?
ChatGPT不仅在互联网和多个行业引发人们的关注,在投资界还掀起了机构对人工智能领域的投资热潮。人工智能聊天程序ChatGPT在去年11月亮相之后,在推出仅两个月后,今年1月份的月活用户已达到了1亿,成为史上增长最快的消费者应用程序…...

代码随想录第十六天(347、194、195、94)
347. 前 K 个高频元素 答案 思路: 1、首先,用到了每个值对应的出现次数,想到要用哈希map存放 2、还需要将出现频率从大到小进行排序,找出前k个元素 3、时间复杂度应该比O(nlogn)小 如果想用快速排序&…...

< elementUI组件样式及功能补全: 实现点击steps组件跳转对应步骤 >
文章目录👉 前言👉 一、效果演示👉 二、点击steps跳转效果实现👉 三、实现案例往期内容 💨👉 前言 在 Vue elementUi 开发中,elementUI中steps步骤条组件只提供了change方法,并未提…...

【学习笔记】互联网金融:芝麻信用分的建模过程
学习资料: 数据分析学习随记 | 互联网金融行业2C授信模型(芝麻信用) 1. 背景 互联网金融的本质是风控。 1.1 数据分析师的角色 数据分析师在金融行业基本上有两种角色: 1.1.1 数据建模师 偏算法,但要很懂业务。要求对算法的理解较深&am…...

Linux C/C++或者嵌入式开发到底有没有35岁危机?
一个读者问了一个问题: 我现在25岁,双非一本本科。在深圳上班,做嵌入式开发,打算走Linux C/C开发,工资目前一般。读了前辈写的很多博客之后,觉得很棒。我现在有一些疑问。 1.最近互联网裁员很厉害嘛&#x…...

国内领先的十大API接口排行
应用程序编程接口API即(Application Programming Interface),现在众多企业的应用系统中常用的开放接口,对接相应的系统、软件功能,简化专业化的程序开发。 一、百度API 百度API超市开通1136个数据服务接口。 网址&a…...
【Linux】Kickstart 配置U盘自动化安装Linux系统
文章目录前言一、刻录USB二、配置以BIOS方式启动引导2.1 引导文件配置2.2 KS文件配置三、以EFI方式启动引导3.1 引导文件3.2 KS文件四、 总结前言 之前安装系统,例如在VMware虚拟机中或物理服务器中,都是根据图形界面上的指示进行下一步这类的操作。 现…...

【Spring MVC】这一篇,带你从入门到进阶
目录 1、什么是MVC? 2、什么是 Spring MVC 3、如何学好 Spring MVC? 3.1、如何创建 Spring MVC 项目 3.1.1、使用Spring Initializr创建(推荐) 3.2、将 Spring 程序与用户(浏览器)联通 3.3、基础注解…...

InstallAware Multi-Platform updated
InstallAware Multi-Platform updated 原生ARM:为您的内置设置、IDE和整个工具链添加了Apple macOS和Linux ARM构建。 本地化:引擎内多语言感知,可再分发工具,具有资产隔离功能,使您的IP保持安全。 模板:将…...

Spring Batch 高级篇-多线程步骤
目录 引言 概念 案例 转视频版 引言 接着上篇:Spring Batch ItemWriter组件,了解Spring Batch ItemWriter处理组件后,接下来一起学习一下Spring Batch 高级功能-多线程步骤 概念 默认的情况下,步骤基本上在单线程中执行&…...

关于iframe一些通讯的记录(可适用工作流审批)
一.知识点(1).我们可以通过postMessage(发送方)和onmessage(接收方)这两个HTML5的方法, 来解决跨页面通信问题,或者通过iframe嵌套的不同页面之间的通信a.父页面代码如下<div v-if"src" class"iframe"><iframeref"iframe"id…...
JavaWeb
1、静态Web html、css 2、动态Web 提供给所有人看的数据始终会发生变化。技术栈:Servlet/JSP,ASP,PHP。 Web应用程序:可以提供浏览器访问的程序。 1、这个统一的web资源会被放在同一个文件夹下,web应用程序-->Tom…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...

基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...