当前位置: 首页 > 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.lryc.cn/news/612787.html

相关文章:

  • 【物联网】基于树莓派的物联网开发【25】——树莓派安装Grafana与Influxdb无缝集成
  • CentOS 7 下通过 Anaconda3 运行llm大模型、deepseek大模型的完整指南
  • 人工智能的20大应用
  • 从Centos 9 Stream 版本切换到 Rocky Linux 9
  • 360纳米AI、实在Agent、CrewAI与AutoGen……浅析多智能体协作系统
  • 构建在 OpenTelemetry eBPF 基础之上:详解 Grafana Beyla 2.5 新特性
  • 【0基础3ds Max】菜单栏介绍
  • 多模态融合(Multimodal Fusion)
  • PCIe Base Specification解析(九)
  • mapbox进阶,mapbox-gl-draw绘图插件扩展,绘制新增、编辑模式支持点、线、面的捕捉
  • 什么是SpringBoot
  • Shuffle SOAR使用学习经验
  • Q-Learning详解:从理论到实践的全面解析
  • 扎根国际数字影像产业园:共享空间助力企业高效发展
  • 施耐德 Easy Altivar ATV310 变频器:高效电机控制的理想选择(含快速调试步骤及常见故障代码)
  • 【3D图像技术分析与实现】谷歌的AlphaEarth是如何实现的?
  • 告别Cursor!最强AI编程辅助Claude Code安装到使用全流程讲解
  • 常见命令-资源查看-iostat命令实践
  • cuda编程笔记(13)--使用CUB库实现基本功能
  • 基于LLM的大数据分析调研
  • 大模型量化原理解析
  • 支持DeepSeek_Qwen等大模型!字狐Chatbox在线模型+本地部署模型
  • 如何封锁品类?提升垂类竞争力
  • leetcode 674.最长连续递增序列
  • 菜鸟笔记007 [...c(e), ...d(i)]数组的新用法
  • 解决 npm i sharp@0.23.4 安装失败异常 npm install sharp异常解决
  • dmctlcvt工具介绍数据文件路径变化后如何拉起数据库
  • 范数的定义、分类与 MATLAB 应用实践
  • agno fastapi对外接口案例
  • 北京JAVA基础面试30天打卡04