🐳Flutter万物皆组件
无论是文本、按钮、图片、布局、动画还是交互逻辑, 都由wideg实现. Flutter 的整个 UI 树本质上是一个由 Widget 嵌套组成的 Widget树.
Widget特性
- 不可变性(immutable): 所有 Widget 都是不可变的(属性一旦创建就不能修改)。当 UI 需要更新时(比如状态变化),只能 创建一个新的 Widget 替换它。
- 组合优于继承: Flutter 不鼓励通过继承扩展 Widget 功能,而是通过组合(将多个简单 Widget 嵌套组合)实现复杂 UI。例如,一个 “带阴影的红色按钮” 可以通过 Container(设置阴影和颜色)包裹 ElevatedButton 实现,而非继承 ElevatedButton 重写样式。
- **既是描述也是配置: **Widget 不仅描述 “UI 长什么样”,还包含了布局规则、交互逻辑、主题样式等配置信息。但 Widget 本身不直接负责渲染,它更像一份 “蓝图”,由 Flutter 引擎通过 Element 和 RenderObject 完成实际渲染(后面会简单说明三者关系)。
🤔为什么 Flutter 要设计成“不可变(immutable)”
这和 Flutter 的渲染机制有关。Flutter 的 UI 渲染本质是 “通过对比新旧 Widget 树,计算出最小更新范围” 的过程(类似 React 的虚拟 DOM diff)。
Flutter 的界面更新过程是这样的:
- 每次状态变化时(比如 setState() 调用后),Flutter 会重新调用 build();
- build() 会返回新的 Widget 树;
- Flutter 比较“旧树”和“新树”,判断哪些节点变了;
- 只更新变动的部分,重新渲染界面。
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” 类别
- 用于组合 / 管理子 Widget 的容器类 Widget
这类 Widget 本身不参与渲染,仅负责组织子 Widget 的结构或逻辑,其核心功能是 “组合” 而非 “绘制”。
- StatelessWidget** 和 StatefulWidget 的自定义子类**,它们的作用是组合其他 Widget(如 Column、Text、Button 等),自身并不需要渲染。
- 用于传递数据的 InheritedWidget 及其子类
InheritedWidget 的核心作用是在 Widget 树中共享数据(如主题、设备信息等),它不参与布局或绘制,因此没有对应的 RenderObject。
- 典型示例:
- Theme:传递应用主题(颜色、字体等),自身不渲染。
- MediaQuery:传递设备尺寸、方向等信息,自身不渲染。
- Localizations:传递多语言文本,自身不渲染。
- 这些 Widget 通过 InheritedElement 管理数据传递,InheritedElement 不会创建 RenderObject,仅负责让子 Widget 可以 “依赖” 并获取数据。
- 用于配置父节点布局逻辑的 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)。
- 用于构建逻辑的辅助 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关系图
🧐Flutter是如何复用Element和RenderObject
很多人知道 Flutter 每次 setState 都会重建 Widget,但其实真正不会频繁重建的是 Element 和 RenderObject。
当 setState() 被调用时,Flutter 会:
- 重新创建新的 Widget 树;
- 与旧的 Widget 树 对比(diff);
- 如果类型或key相同,则复用对应的 Element 和 RenderObject;
- 仅更新需要变动的属性;
- 避免整个界面重绘。
Element 层复用
Flutter 的 Element 树是真正持久存在的对象树。
当 Flutter 重新构建时,会调用:
Element.update(newWidget)
在这个方法中:
- 判断新旧 Widget 类型是否相同;
- 如果相同:
- 保留当前 Element;
- 更新其中保存的 Widget 引用;
- 调用 widget.updateRenderObject(context, renderObject) 来同步属性;
- 如果类型不同:
- 销毁旧 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