Flutter 08 三棵树(Widgets、Elements和RenderObjects)

news2024/11/25 4:20:47

一、Flutter三棵树背景

1.1 先思考一些问题

1. Widget与Element是什么关系?它们是一一对应的还是怎么理解?

2. createState 方法在什么时候调用?state 里面为啥可以直接获取到 widget 对象?

3. Widget 频繁更改创建是否会影响性能?复用和更新机制是什么样的?

4. Widget、Element、RenderObject 三棵树之间的关系是怎样的?

1.2 Flutter中Dom树

如何理解 DOM 树这个概念

它由页面中每一个控件组成,这些控件所形成的一种天然的嵌套关系使其可以表示为 “树” 结构,可以将 这个概念应用在 Flutter 中。

例如默认的计数器应用的结构如下图:

二、Flutter中的三棵树 

即Widget树、Element树和RenderObject树。

Widget树:控件的配置信息,不涉及渲染,更新代价极低。

RenderObject树:真正的UI渲染树,负责渲染UI,更新代价极大。

Element树:Widget树和RenderObject树之间的粘合剂,负责将Widget树的变更以最低的代价映射到 RenderObject树上。

Widget 树

我们平时用 Widget 使用声明式的形式写出来的界面,可以理解为 Widget 树,这是要介绍的第一棵树。 Widget的功能是“描述一个UI元素的配置数据”,它就是说,Widget其实并不是表示最终绘制在设备屏幕 上的显示元素,而它只是描述显示元素的一个配置数据。

RenderObject 树

Flutter 引擎需要把我们写的 Widget 树的信息都渲染到界面上,这样人眼才能看到,跟渲染有关的当然 有一颗渲染树 RenderObject tree,这是第二颗树,渲染树节点叫做 RenderObject,这个节点里面处理 布局、绘制相关的事情。

这两个树的节点并不是一一对应的关系,有些 Widget是要显示的,有些 Widget ,比如那些继承自 StatelessWidget & StatefulWidget 的 Widget 只是将其他 Widget 做一个组合,这些 Widget 本身并不 需要显示,因此在 RenderObject 树上并没有相对应的节点。

Element 树

Widget 树是非常不稳定的,动不动就执行 build方法,一旦调用 build 方法意味着这个 Widget 依赖的 所有其他 Widget 都会重新创建,如果 Flutter 直接解析 Widget树,将其转化为 RenderObject 树来直 接进行渲染,那么将会是一个非常消耗性能的过程,那对应的肯定有一个东西来消化这些变化中的不 便,来做cache。

因此,这里就有另外一棵树 Element 树。Element 树这一层将 Widget 树的变化(类似 React 虚拟 DOM diff)做了抽象,可以只将真正需要修改的部分同步到真实的 RenderObject 树中,最大程度降低 对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。

三、Flutter三棵树关系

三棵树架构关系

三棵树架构图:

总结的关系:

widget 树和 Element 树节点是一一对应关系,每一个 Widget 都会有其对应的 Element,但是 RenderObject 树则不然,只有需要渲染的 Widget 才会有对应的节点。

Element 树相当于一个中间层,大管家,它对 Widget 和 RenderObject 都有引用。

当 Widget 不断变化的时候,将新 Widget 拿到 Element 来进行对比,看一下和之前保留的 Widget 类 型和 Key 是否相同,如果都一样,那完全没有必要重新创建 Element 和 RenderObject,只需要更新里 面的一些属性即可,这样可以以最小的开销更新 RenderObject,引擎在解析 RenderObject 的时候,发 现只有属性修改了,那么也可以以最小的开销来做渲染。

简单总结一下:

Widget 树就是配置信息的树,我们平时写代码写的就是这棵树。

RenderObject 树是渲染树,负责计算布局,绘制,Flutter 引擎就是根据这棵树来进行渲染的。

Element 树作为中间者,管理着将 Widget 生成 RenderObject和一些更新操作。

举个通俗例子:

UI 渲染就像盖一栋大楼,Widget 代表图纸,表示我们想造怎样的大楼,RenderObject 是根据图纸干活 的工人,而 Element 是监工,负责协调各方资源,统一调配,外部人员有事需要先找这个监工。 

三者创建关系图:

用文字描述三者创建关系

首先是 Widget 通过调用其 createElement 方法创建出 Element 对象。

Element 继续调用其持有 Widget 对象(Stateless)或 State 对象(Stateful)的 build 方法创建其子 widget 对象。往复循环,继续创建子Element,子 Element 持有父 Element 的引用,因此最终会形成 出一颗 Element 树。

对于有 layout/paint 的能力控件,会创建 RenderObjectElement,在该 Element 的 mount 阶段会创 建其对应的 RenderObject 对象。 

四、运行时三棵树结构

三棵树结构

认识了三棵树之后,那Flutter是如何创建布局的?以及三棵树之间他们是如何协同的呢?

接下来就让我们通过一个简单的例子来剖析下它们内在的协同关系:

class Tree extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.brown,
      child: Row(
        children: [
          new Image.network(
            "https://p1.ssl.qhmsg.com/dr/220__/t01d5ccfbf9d4500c75.jpg",
            width: 100,
            height: 100,
          ),
          new Text(
            "从网络加载图片",
            style: TextStyle(fontSize: 16),
          ),
        ],
      ),
    );
  }
}

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

1.Flutter会构建包含Widget(Container,Row,Image,Text)的Widgets树;

2.Flutter遍历Widget树,然后根据其中的Widget调用createElement()来创建相应的Element对 象,最后将这些对象组建成Element树;

3.接下来会创建第三个树,这个树中包含了与Widget对应的Element通过createRenderObject() 创建的RenderObject;

具体Flutter经过这三个步骤后的状态:

总结一下三棵树结构:

1. Widget Tree: Widget 是 Flutter 面向开发者的上层接口,我们通过 widget 的层层嵌套,会形成 一颗 Widget 树,一个 Widget 可在多个位置复用。Flutter Framework 层为我们提供了一些常用 的包装或者容器的 Widget,比如 Container,其内部继续嵌套了其他 Widget,如 Padding、Align 等等。所以,开发者编写的 Widget 树和实际生成的 Widget 树都会略有差别。如图中虚线圆形标 注的 ColorBox、RawImage 等。

2. Element Tree :每一个 Widget 都会对应一个 Element,只不过 Element 分类不同。

3. RenderObject Tree:RenderObject 只负责最终的测量、布局和绘制,因此最终的 RenderObject Tree 是 Element Tree 剔除掉哪些包装,最后组织而成的 Tree。

为何搞这多树?

分层:开发只关注widget

1. Framework 将复杂的内部设计、渲染逻辑与开发接口隔离开,应用层只需关注 Widget 开发即 可。

高效:提交绘制效率

1. Tree 最大的共同特点就是快取,因为 Element、RenderObject 销毁重建成本很高,一旦可以复用 ,那么快取可以大幅减少这种开销。

2. 比如:当 Element 不需要重建时,更新 Widget 的引用就可以了;Layer Tree 的设计是将绘制图层 分开,方便提取和合成,合成层中的 transform 和 opacity 效果,都只是几何变换、透明度变换 等,不会触发 layout 和 paint,直接由 GPU 完成即可。

五、三棵树的作用介绍

1. 简而言之是为了性能,为了复用Element从而减少频繁创建和销毁RenderObject。

2. 因为实例化一个RenderObject的成本是很高的,频繁的实例化和销毁RenderObject对性能的影响 比较大,所以当Widget树改变的时候,Flutter使用Element树来比较新的Widget树和原来的 Widget树:

//framework.dart
@protected
Element 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;
}

1. 如果某一个位置的Widget和新Widget不一致,才需要重新创建Element;

2. 如果某一个位置的Widget和新Widget一致时(两个widget相等或runtimeType与key相等),则只需 要修改RenderObject的配置,不用进行耗费性能的RenderObject的实例化工作了;

3. 因为Widget是非常轻量级的,实例化耗费的性能很少,所以它是描述APP的状态(也就是 configuration)的最好工具;

4. 重量级的RenderObject(创建十分耗费性能)则需要尽可能少的创建,并尽可能的复用; 

更新时三棵树操作

因为Widget是不可变的,当某个Widget的配置改变的时候,整个Widget树都需要被重建。

1. 例如当我们改变一个Text文本的时候,框架就会触发一个重建整个Widget树的动作。

2. 因为有了Element的存在,Flutter会比较新的Widget树中的第一个Widget和之前的 Widget。

3. 接下来比较Widget树中之后Widget和之前Widget,以此类推,直到Widget树比较完成。

@override
Widget build(BuildContext context) {
  return Container(
    color: Colors.brown,
    height: double.infinity,
    child: Row(
      children: [
        new Image.network(
          "https://p1.ssl.qhmsg.com/dr/220__/t01d5ccfbf9d4500c75.jpg",
          width: 100,
          height: 100,
        ),
        new Text(
          "改变UI",
          style: TextStyle(
              fontSize: 16
          ),
        ),
      ],
    ),
  );
}

Flutter遵循一个最基本的原则:

判断新的Widget和老的Widget是否是同一个类型:

如果不是同一个类型,那就把Widget、Element、RenderObject分别从它们的树(包括它们的子树)上 移除,然后创建新的对象;

如果是一个类型,那就仅仅修改RenderObject中的配置,然后继续向下遍历。 

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

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

相关文章

手把手教程 | YOLOv8-seg训练自己的分割数据集

🚀🚀🚀手把手教程:教会你如何使用自己的数据集开展分割任务 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,轻轻松…

基于卷尾猴算法的无人机航迹规划-附代码

基于卷尾猴算法的无人机航迹规划 文章目录 基于卷尾猴算法的无人机航迹规划1.卷尾猴搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要:本文主要介绍利用卷尾猴算法来优化无人机航迹规划。 1.卷尾猴…

C++入门学习(1)命名空间和输入输出

前言 在C语言和基本的数据结构学习之后,我们终于迎来了期待已久的C啦!C发明出来的意义就是填补一些C语言的不足,让我们更加方便的写代码,所以今天我们就来讲一下C语言不足的地方和在C中的解决办法! 一、命名空间 在学习…

云产品 FC 免费试用获取奖励完整步骤

文章目录 步骤 1:获取活动链接步骤 2:报名参加步骤 3:试用产品步骤 4:部署步骤 5:访问应用步骤 6:生成小说步骤 7:提交作品步骤 8:提交任务获取奖励 🎉云产品 FC 免费试用…

动态规划(Dynamic Programming)—— Java解释

一、基本思想 动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,并将子问题的求解结果存储起来避免重复求解,从而一步步获取最优解的处理算法。 动态规划算法与分治算法类似,其基本思想也是将待求解…

【Solidity】Solidity中的基本数据类型和复合数据类型

1. 基本数据类型 1.1 整数类型 Solidity支持有符号整数和无符号整数,可以指定位数和范围。以下是一些整数类型的示例: int:有符号整数,可以是正数或负数。2,-45,2023 uint:无符号整数&#x…

系统提示缺少或找不到d3dcompiler_43.dll文件的详细修复教程

今天我来给大家分享一下关于d3dcompiler_43.dll缺失的4个修复方法。 首先,我们来了解一下d3dcompiler_43.dll的作用。它是DirectX中的一个组件,用于编译Shader和Pixel着色器代码。如果缺少了这个文件,就会导致游戏或应用程序无法正常运行。 …

操作系统复习(2)进程管理

一、概述 1.1程序的顺序执行 一个具有独立功能的程序独占CPU运行,直至得到最终结果的过程称为程序的顺序执行。 程序的并发执行所表现出的特性说明两个问题 ⑴ 程序和计算机执行程序的活动不再一一对应 ⑵ 并发程序间存在相互制约关系(要求共享信息&…

Docker容器中执行throttle.sh显示权限报错:RTNETLINK answers: Operation not permitted

在模拟通信环境时,我执行了一下命令: bash ./throttle.sh wan但是,出现了权限的报错:RTNETLINK answers: Operation not permitted 解决方案说简单也挺简单,只需要两步完成。但是其实又蛮繁琐,因为需要将…

伪随机序列——m序列及MATLAB仿真

文章目录 前言一、m 序列1、m 序列的产生2、m 序列的性质①、均衡性②、游程分布③、移位相加特性④、自相关函数⑤、功率谱密度⑥、伪噪声特性 二、M 序列1、m 序列的产生2、m 序列的性质 三、MATLAB 中 m 序列1、m 序列生成函数的 MATLAB 代码2、MATLAB 仿真 前言 在通信系统…

15 _ 二分查找(上):如何用最省内存的方式实现快速查找功能?

今天我们讲一种针对有序数据集合的查找算法:二分查找(Binary Search)算法,也叫折半查找算法。二分查找的思想非常简单,很多非计算机专业的同学很容易就能理解,但是看似越简单的东西往往越难掌握好,想要灵活应用就更加困难。 老规矩,我们还是来看一道思考题。 假设我们…

【Android】Android Framework系列---CarPower深度睡眠STR

Android Framework系列—CarPower深度睡眠 之前博客的说说CarPower的开机启动流程 这里分析一下,Android CarPower实现深度睡眠的流程。 首先,什么是深度睡眠(Deep Sleep)? Android进入Deep Sleep后,关闭屏幕、关闭CPU的电源,保…

【Head First 设计模式】-- 观察者模式

背景 客户有一个WeatherData对象,负责追踪温度、湿度和气压等数据。现在客户给我们提了个需求,让我们利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。 WeatherData对象提供了4个接口: …

【0基础学Java第七课】-- 类和对象01

7. 类和对象 7.1 面向对象的初步认知7.1.1 什么是面向对象7.1.2 面向对象与面向过程 7.2 类定义和使用7.2.1 简单认识类7.2.2 类的定义格式7.2.3 定义一个狗类7.2.4 定义一个学生类 7.3 类的实例化7.3.1 什么是实列化7.3.2 引用只能指向对象,且不能同时指向多个对象…

C#使用随机数模拟英雄联盟S13瑞士轮比赛

瑞士轮赛制的由来 瑞士制:又称积分循环制,最早出现于1895年在瑞士苏黎世举办的国际象棋比赛中,故而得名。其基本原则是避免种子选手一开始就交锋、拼掉,是比较科学合理、用得最多的一种赛制;英语名称为Swiss System。…

微信内H5页面唤醒App

首先,简述一下这个需求的背景,产品希望能够让用户在微信内,打开一个h5页面,然后就能唤醒公司中维护的app,这个是为了能够更好的引流。 唤醒app的三种方案 IOS系统-Universal Link(通用链接) …

invoke方法传参String数组问题——wrong number of arguments

invoke方法传参String数组问题——wrong number of arguments 问题描述一、案例准备二、错误反射调用实例三、正确反射调用实例 问题描述 今天笔者在使用invoke方法的时候,发现报了一个这样一个错:“wrong number of arguments”,在网上冲浪…

【Python】-- python的基本图像处理(图像显示、保存、颜色变换、缩放与旋转等)

目录 一、图像文件的读写 操作步骤: 显示图像文件的三个常用属性: 例: 二、图像文件的处理 常用的图像处理方法 1、图像的显示 2、图像的保存 3、图像的拷贝与粘贴 4、图像的缩放与旋转 5、图像的颜色变换 6、图像的过滤与增强 7、序…

【MySQL】用户管理权限控制

文章目录 前言一. 用户管理1. 创建用户2. 删除用户3. 修改用户密码 二. 权限控制1. 用户授权2. 查看权限3. 回收权限 结束语 前言 MySQL的数据其实也以文件形式保存,而登录信息同样保存在文件中 MySQL的数据在Linux下默认路径是/var/lib/mysql 登录MySQL同样也可以…

全网超细,Pytest自动化测试框架入门到精通-实战整理,一篇打通...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、Pytest和Unitt…