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

Flutter 三棵树

Flutter 三棵树

Widget :配置与描述

  • 它是一个**不可变(immutable)**的蓝图。你不能在运行时去修改一个Widget的属性,你只能用一个新的Widget来替换它。
  • 它告诉Flutter:“我想要一个长什么样的UI元素,它应该有什么样的配置(颜色、文本、子组件等)。”
  • 轻量级:创建和销毁Widget对象的成本极低。

Element:对比、生命周期管理与上下文

  • 对比 (Diffing):这是它的核心职责之一。当新的Widget蓝图下来时,Element负责将新旧Widget进行对比,决定是更新现有节点还是销-毁并重建。这是key发挥作用的地方。
  • 生命周期管理 (Lifecycle Management)Element才是真正“活着”的对象。它管理着状态(对于StatefulWidget)和生命周期(mount, unmount, deactivate)。我们熟悉的State对象就是由Element持有和管理的。
  • 上下文 (Context):每个Element都是一个BuildContext。当我们在build方法中使用context时(例如Theme.of(context)),我们实际上是在向Element树查询信息。它扮演着一个“大管家”和“信息中心”的角色,连接着树中的各个节点。

RenderObject:布局计算、绘制、合成、命中测试

  • 计算 (Layout):它负责所有尺寸和位置的计算。它会执行一个“2-pass layout”过程:
    1. 父节点向子节点传递约束(“你最多可以这么宽/高”)。
    2. 子节点根据约束计算自己的尺寸,并报告给父节点。
    3. 父节点根据子节点的尺寸,确定它们最终的位置。
  • 渲染 (Painting & Compositing)
    • Painting: 计算完成后,RenderObject负责将自己“画”出来。
    • Compositing: 对于复杂的UI,它还会创建“图层”(Layers),将绘制好的内容组合起来,以便GPU可以高效地处理和渲染,实现流畅的动画和效果。
  • 命中测试 (Hit Testing):当你点击屏幕时,是RenderObject树负责判断你到底点中了哪个UI元素。

例子

好的,我们继续用盖房子的比喻来讲述当一个布局改变时,这三棵树是如何协同工作的。

假设我们原来的布局是这样的(这是我们的旧蓝图 Widget):```dart// 旧蓝图Column( children: [ Text(‘你好’), Icon(Icons.star), ],)

现在,您修改了代码,想要改变布局,把`Column`(垂直排列)换成`Row`(水平排列)。这是我们的**新蓝图 `Widget` 树**:
```dart
// 新蓝图
Row(children: [Text('你好'),Icon(Icons.star),],
)

当您保存代码并触发热重载(Hot Reload),或者调用setState时,Flutter的更新流程就开始了。


更新流程详解

第1步:Flutter拿到新蓝图,开始与“施工队”核对

Flutter框架拿到了你的新Widget树(Row…),然后它会找到对应的“施工队”——Element

它从Element树的根节点开始,一层一层地向下对比新旧Widget

第2步:Element树的对比与决策(最关键的一步)

Element施工队非常聪明,它遵循两个核心原则:

  1. 如果新旧Widget类型key相同,就复用这个Element,只更新Widget的配置。
  2. 如果新旧Widget类型key不同,就丢弃旧的Element(以及它下面的所有子Element),创建一个全新的Element

现在,让我们看看施工队对比的过程:

  • 对比第一层

    • 旧蓝图Column
    • 新蓝图Row
    • 施工队(Element)决策“类型不同!” (Column != Row)。 “好了,旧的Column施工员和它手下所有的人(包括TextIcon的施工员)全部解雇,一个不留!”
    • 动作
      1. Column对应的Element被标记为“待销毁”(deactivate)。
      2. Column对应的RenderObject(负责垂直布局的工人)也被通知“你可以下班了”。
      3. 所有子ElementTextIcon的)和它们对应的RenderObject也一并被销毁。
      4. 根据新的Row蓝图,创建一个全新的RowElement
  • 对比第二层(在新的Row Element下进行)

    • 新的Row施工员(Element)开始为它的孩子们创建骨架。
    • 它看到新蓝图里有Text('你好')Icon(Icons.star)
    • 于是,它创建了全新的TextElement全新的IconElement
第3步:Element树指挥RenderObject树进行施工

现在,新的Element骨架已经搭建好了,它们需要去指挥“实体工人”(RenderObject树)干活。

  • 新的RowElement会创建一个新的RenderFlex对象(这是RowColumn背后的布局工人),并告诉它:“你的任务是水平排列direction: Axis.horizontal)。”
  • 新的TextElement会创建一个新的RenderParagraph对象,并告诉它:“去把‘你好’画出来。”
  • 新的IconElement会创建一个新的RenderParagraph对象(图标在底层也是通过字体文件绘制的),并告诉它:“去把‘星星’这个图标画出来。”
第44步:RenderObject树执行布局和绘制

现在,全新的RenderObject工人们开始工作:

  1. 布局 (Layout)
  • RowRenderObject会问它的孩子们(TextIconRenderObject):“你们各自想占多大地方?”
  • TextIconRenderObject计算并报告自己的尺寸。
  • RowRenderObject拿到孩子们的尺寸后,按照水平方向将它们依次摆放好,计算出它们在屏幕上的最终坐标。
  1. 绘制 (Paint)
  • 布局完成后,Flutter的渲染引擎会说:“好了,所有东西的位置和大小都定下来了,开画!”
  • 它会遍历RenderObject树,依次调用每个工人的paint方法,让它们把自己画在屏幕上。Text画出文字,Icon画出图标。

最终,你在屏幕上看到了从垂直布局变成水平布局的新界面。


如果只是改变属性,而不是类型呢?

如果我们只是把Text('你好')改成Text('再见')Column不变。

  • Element对比
    • 第一层:旧Column vs 新Column类型相同!复用ColumnElement
    • 第二层:旧Text vs 新Text类型相同!复用TextElement
  • Element决策
    • ColumnElement被复用,它对应的RenderObject被复用
    • TextElement被复用,它对应的RenderObject被复用
  • 更新操作
    • 被复用的TextElement发现它的配置变了(文字内容从“你好”变成了“再见”)。
    • 它会告诉它管理的RenderObject:“嘿,别的都不用变,把墙上的字擦了,重新画上‘再见’就行了。”
  • RenderObject工作
    • TextRenderObject只需要重新计算一下新文字的大小(可能需要重新布局),然后重新绘制这部分文字。
    • ColumnRenderObject因为孩子的大小可能变了,所以也需要重新布局,但它本身不需要重绘。

总结

  • 改变布局类型 (如Column -> Row):会导致Element树和RenderObject树在改变点大规模地销毁和重建,开销较大。
  • 只改变属性 (如color, text):会尽可能地复用ElementRenderObject,只在必要时更新属性、重新布局或重绘,开销小得多。

这就是为什么Flutter鼓励我们使用小的、组合的Widget,并将状态管理放在尽可能低的层级,因为这样可以把UI更新的范围限制在最小,从而获得最佳性能。

Key的作用

帮助Flutter在Widget树更新时,更精确地识别和匹配Element

http://www.dtcms.com/a/319479.html

相关文章:

  • 数字取证:可以恢复手机上被覆盖的数据吗?
  • 【免费】小学数学算术专项突破随机生成加法减法乘法除法
  • 无人机计算机视觉数据集-7,000 张图片 空域安全监管 无人机反制系统 智能安防监控 交通执法应用 边境管控系统 赛事安保服务
  • 香港网站服务器被占用的资源怎么释放?
  • 《深入Java包装类体系:类型转换原理与Integer缓存实战指南》
  • 基于IPD流程体系的研发项目计划管理
  • Go 开发环境配置完整指南
  • 如何将普通HTTP API接口改造为MCP服务器
  • Numpy科学计算与数据分析:Numpy数组属性入门之形状、维度与大小
  • Node.js特训专栏-实战进阶:21.Nginx反向代理配置
  • Spring MVC文件上传详解
  • 使用 Tauri 开发 Android 应用:环境搭建与入门指南
  • Android 之 面试八股文
  • MySQL GROUP BY 语句详细说明
  • 什么是负载均衡,有哪些常见算法?
  • 计算机硬件组成原理
  • 复合机器人破局之路:如何逆袭突围
  • day 48 模型的可视化与推理
  • Spring Cloud 项目注册 Nacos 时设置真实 IP 的多种方式【多网卡/虚拟机实用指南】
  • 电子设计项目/复刻入门指南(从0到1的蜕变)--(持续更新...)(附完整项目举例)
  • 阿里云OSS vs 腾讯云COS深度对比:如何为网站静态资源选择最佳对象存储?
  • vue2+elementui select框可以选择可以回车添加新的option
  • CD61.【C++ Dev】多态(1)
  • 腾讯云EdgeOne产品深度分析报告
  • Docker入门教程:在腾讯云轻量服务器上部署你的第一个容器化应用 (2025)
  • 基于Matlab图像处理的黄豆自动计数系统设计与实现
  • 【数据结构入门】双向链表
  • Windows中安装rustup-init.exe以及cargo build报错443
  • ENSP 中静态路由负载分担
  • linux开发之mmap内存映射