🐳Flutter万物皆组件

无论是文本、按钮、图片、布局、动画还是交互逻辑, 都由wideg实现. Flutter 的整个 UI 树本质上是一个由 Widget 嵌套组成的 Widget树.

Widget特性

  1. 不可变性(immutable): 所有 Widget 都是不可变的(属性一旦创建就不能修改)。当 UI 需要更新时(比如状态变化),只能 创建一个新的 Widget 替换它
  2. 组合优于继承: Flutter 不鼓励通过继承扩展 Widget 功能,而是通过组合(将多个简单 Widget 嵌套组合)实现复杂 UI。例如,一个 “带阴影的红色按钮” 可以通过 Container(设置阴影和颜色)包裹 ElevatedButton 实现,而非继承 ElevatedButton 重写样式。
  3. **既是描述也是配置: **Widget 不仅描述 “UI 长什么样”,还包含了布局规则、交互逻辑、主题样式等配置信息。但 Widget 本身不直接负责渲染,它更像一份 “蓝图”,由 Flutter 引擎通过 Element 和 RenderObject 完成实际渲染(后面会简单说明三者关系)。

🤔为什么 Flutter 要设计成“不可变(immutable)”

这和 Flutter 的渲染机制有关。Flutter 的 UI 渲染本质是 “通过对比新旧 Widget 树,计算出最小更新范围” 的过程(类似 React 的虚拟 DOM diff)。
Flutter 的界面更新过程是这样的:

  1. 每次状态变化时(比如 setState() 调用后),Flutter 会重新调用 build();
  2. build() 会返回新的 Widget 树
  3. Flutter 比较“旧树”和“新树”,判断哪些节点变了;
  4. 只更新变动的部分,重新渲染界面。

Widget 树: UI 的蓝图

想象一下:

  • Widget 像一张房子的蓝图;
  • Element 是建筑工人;
  • RenderObject 是实际盖出来的房子。
    当你想换一个窗户颜色(比如文字从“Hello”改为“Hi”)时,
    Flutter 会:
  • 生成一张新的蓝图(新的 Widget);
  • 工人(Element)拿到新蓝图,只在必要的地方修改房子(RenderObject)。
    这样做可以:
  • 保持逻辑简单;
  • 避免直接操作界面导致的混乱;
  • 让界面更新更高效。

Element 树: Render Object和Widget之间的桥梁

每个 Widget 在界面中被插入时,都会对应一个 Element, Element是可变的。

它的主要职责包括

功能 说明
持有 Widget 的实例 每个 Widget 都有对应的 Element
维护父子层级结构 形成 Element Tree(运行时真正的 UI 树)
管理生命周期 控制 build / update / remove
持有渲染对象引用 如果 Widget 需要绘制,会持有 RenderObject.

💡 典型的 “无 RenderObject 的 Widget” 类别

  1. 用于组合 / 管理子 Widget 的容器类 Widget
    这类 Widget 本身不参与渲染,仅负责组织子 Widget 的结构或逻辑,其核心功能是 “组合” 而非 “绘制”。
  • StatelessWidget** 和 StatefulWidget 的自定义子类**,它们的作用是组合其他 Widget(如 Column、Text、Button 等),自身并不需要渲染。
  1. 用于传递数据的 InheritedWidget 及其子类
    InheritedWidget 的核心作用是在 Widget 树中共享数据(如主题、设备信息等),它不参与布局或绘制,因此没有对应的 RenderObject。
  • 典型示例:
  • Theme:传递应用主题(颜色、字体等),自身不渲染。
  • MediaQuery:传递设备尺寸、方向等信息,自身不渲染。
  • Localizations:传递多语言文本,自身不渲染。
  • 这些 Widget 通过 InheritedElement 管理数据传递,InheritedElement 不会创建 RenderObject,仅负责让子 Widget 可以 “依赖” 并获取数据。
  1. 用于配置父节点布局逻辑的 ParentDataWidget 子类
    ParentDataWidget 的作用是向父 Widget 的布局逻辑传递配置信息(如子 Widget 在父容器中的位置、尺寸约束等),自身不参与渲染。
  • 典型示例:
  • Positioned(配合 Stack 使用):指定子 Widget 在 Stack 中的位置(left/top/right/bottom),但 Positioned 本身不渲染,仅将位置信息传递给 Stack 的 RenderObject(RenderStack)。
  • Flexible/Expanded(配合 Row/Column 使用):指定子 Widget 在弹性布局中的占比,自身不渲染,仅将弹性系数传递给 Row/Column 的 RenderObject(RenderFlex)。
  1. 用于构建逻辑的辅助 Widget
    部分 Widget 仅用于封装 “构建逻辑”(如条件渲染、动态生成子 Widget),自身不参与渲染。
  • 典型示例:
  • Builder:仅接收一个 builder 函数并返回子 Widget,用于在构建时获取 BuildContext,自身无 RenderObject。
  • StatefulBuilder:类似 Builder,但支持在构建时管理临时状态,自身不渲染。
    核心原因:职责分离
    Flutter 设计中,RenderObject 的核心职责是布局、绘制、命中测试(直接与渲染相关)。而上述 Widget 的职责是组合、管理、传递数据,这些工作不需要参与底层渲染,因此无需创建 RenderObject。
    通过这种设计,Flutter 确保了 “渲染树(RenderObject 树)” 只包含必要的渲染节点,避免了冗余计算,提升了性能。

Render Object树: 渲染的原动力

RenderObject 是真正 执行布局、绘制、命中测试(点击检测) 的对象, 它是可变的。它构成了 Flutter 的底层渲染树(Render Tree)。

他的主要职责包括

功能 说明
布局(layout) 测量子节点大小、确定自己位置
绘制(paint) 绘制到屏幕的 Canvas 上
命中测试(hitTest) 判断点击事件是否落在自己范围内
性能优化 Flutter 会复用 RenderObject 以避免重复构建

Widget Tree Element tree和RenderObject tree关系图
image

🧐Flutter是如何复用Element和RenderObject

很多人知道 Flutter 每次 setState 都会重建 Widget,但其实真正不会频繁重建的是 Element 和 RenderObject

当 setState() 被调用时,Flutter 会:

  1. 重新创建新的 Widget 树
  2. 与旧的 Widget 树 对比(diff)
  3. 如果类型或key相同,则复用对应的 Element 和 RenderObject;
  4. 仅更新需要变动的属性;
  5. 避免整个界面重绘。

Element 层复用

Flutter 的 Element 树是真正持久存在的对象树。
当 Flutter 重新构建时,会调用:
Element.update(newWidget)
在这个方法中:

  1. 判断新旧 Widget 类型是否相同;
  2. 如果相同:
  • 保留当前 Element;
  • 更新其中保存的 Widget 引用;
  • 调用 widget.updateRenderObject(context, renderObject) 来同步属性;
  1. 如果类型不同:
  • 销毁旧 Element;
  • 创建新的 Element(和对应 RenderObject)。
    举例说明
Text('Count: 1');
// 变成
Text('Count: 2');

Flutter 检查:

  • 新旧 Widget 类型都为 Text ✅
  • 旧的 Element 可复用 ✅
  • 调用 update() 更新文字内容
  • 底层 RenderParagraph(RenderObject)继续复用,只重新绘制文字
    所以虽然你每次都在 “build()” 新 Widget,实际上底层对象和绘制性能几乎没变。

RenderObject层复用

在 RenderObjectWidget(如 Container, Text, Image)中,
每个 Widget 都会对应一个 RenderObject。
当 Element 判断可以复用时,会执行:
widget.updateRenderObject(context, renderObject);
这个方法内部只更新属性,不会重新创建对象。
例如 RenderParagraph:

@override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
  renderObject
    ..text = textSpan
    ..textAlign = textAlign
    ..maxLines = maxLines;
}

这意味着:

  • 同一个 RenderObject 实例继续存在;
  • 只修改变动的属性;
  • 布局系统只重新计算局部变化;
  • 渲染层只重绘必要区域。

总结

特性 说明
Widget 可重建但轻量 只是配置描述,创建成本低
Element 可复用 避免频繁创建对象树
RenderObject 局部更新 只重绘变化部分,节省 GPU/CPU
全树 Diff 优化 Flutter 通过 O(N) 算法比较树结构,避免昂贵的虚拟 DOM 对比

References:
How Flutter Renders Widgets: A Tale of Widgets, Elements, and Render Objects
Understanding Keys, Elements, RenderObjects and their interplay in Flutter