一、概述
这篇博客主要介绍Flutter中的三棵树。
二、Flutter中的三棵树
这是 Flutter 最核心的原理之一,理解这三棵树的关系至关重要。
1. Widget 树(配置描述)
Widget 是不可变的配置信息,它描述了 UI 应该长什么样。每次 setState() 调用都会重建 Widget 树。
1.Widget的本质
Widget的源码如下:
dart
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({this.key});
final Key? key;
@protected
@factory
Element createElement();
@override
String toStringShort() {
final String type = objectRuntimeType(this, 'Widget');
return key == null ? type : '$type-$key';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
@override
@nonVirtual
bool operator ==(Object other) => super == other;
@override
@nonVirtual
int get hashCode => super.hashCode;
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
}
static int _debugConcreteSubtype(Widget widget) {
return widget is StatefulWidget
? 1
: widget is StatelessWidget
? 2
: 0;
}
}
从源码中,我们可以看出源码顶层标记 @immutable 不可变注解,且所有属性(Key)全部为 final 修饰。这是 Flutter 核心设计:Widget 是纯配置模板,禁止运行时修改,保证新旧组件比对的准确性,为 Diff 算法、局部刷新提供底层支撑。
然后我们看到有一个createElement 方法,Widget类是一个抽象类,定义了核心抽象方法 createElement(),由子类实现:StatefulWidget 创建 StatefulElement,StatelessWidget 创建 StatelessElement。是Widget 绑定 Element、初始化三棵树的唯一入口。
还有一个重要的方法是canUpdate方法,静态方法 canUpdate(oldWidget, newWidget) 是 Flutter 组件复用的核心规则:只有 runtimeType 一致 + Key 一致,才复用旧组件,否则销毁重建。
2.Widget的种类
|--------------------|-------------------|--------------------------------|
| 类型 | 特点 | 典型代表 |
| StatelessWidget | 无状态,纯展示 | Text、Icon、Container |
| StatefulWidget | 持有 State,可变 | Checkbox、TextField、ListView |
| RenderObjectWidget | 直接关联 RenderObject | Opacity、Transform、 CustomPaint |
3.Widget的轻量级性验证
我们可以使用官方的计时器统计下创建Widget的耗时。
void main() {
final stopwatch = Stopwatch()..start();
for (int i = 0; i < 100000; i++) {
Text('Hello $i');
}
stopwatch.stop();
debugPrint('10 万次创建耗时: ${stopwatch.elapsedMilliseconds}ms');
}
执行上述的代码之后,控制台输出的时间为10ms(一般在20-~50ms),也就是我们创建10万个Widget耗时为10ms。
这说明Widget 本质上就是一个普通 Dart 对象,只有几个字段,GC 友好。你可以放心地在
build() 里返回全新的 Widget 树,性能瓶颈从来不在 Widget 创建上。
4.const 构造函数的威力
const 构造函数让 Widget 在编译期就确定,运行时完全复用同一实例:
在下面的代码中:
class MyWidget extends StatelessWidget {
const MyWidget({super.key}); // ← const 构造
@override
Widget build(BuildContext context) {
return const Text('Hello'); // ← const 实例,全局唯一
}
}
// 在父组件中:
Widget build(BuildContext context) {
return const MyWidget(); // 永远是同一个实例
// → Element.updateChild() 中 identical(old, new) == true
// → 直接 return child,跳过整个 diff 过程
}
const Widget 在 diff 时会被 identical() 检测为同一实例,框架直接跳过更新。这是 Flutter 最
廉价、最有效的性能优化手段。养成「能 const 就 const」的习惯。
2. Element 树(生命周期实体)
Element 是 Widget 在树中的实例化,它管理着 Widget 的更新和生命周期。当 Widget 树重建时,Flutter 会复用对应的 Element(如果 runtimeType 和 key 相同),从而高效更新 UI。
1.Element的核心职责
|---------|-------------------------|--------------------------|
| 职责 | 方法 | 说明 |
| 持有引用 | _widget, renderObject | 绑定 Widget 和 RenderObject |
| 生命周期 | mount/unmount | 控制组件挂载和卸载 |
| Diff 比对 | updateChild() | 决定复用/更新/销毁 |
| 重建调度 | markNeedsBuild() | 将自己加入脏列表 |
| 依赖管理 | dependOnInheritedWidget | InheritedWidget 订阅 |
2.Element的核心属性
abstract class Element {
Element? _parent; // 父 Element
Widget? _widget; // 当前绑定的 Widget
RenderObject? renderObject; // 对应的渲染节点
bool _active = true; // 是否在树中活跃
int _depth = 0; // 树的深度
List<Element>? _children; // 子 Element 列表
}
3.Diff 算法:updateChild 全流程
这是 Flutter 渲染最核心的算法,决定了 Element 是否复用。
updateChild 是 Flutter 单节点 Diff 的核心源码方法,位于 Element,setState → rebuild → performRebuild 执行 build 拿到新 Widget 之后,就调用updateChild对子节点做差异化更新。 注意:updateChild处理单个子节点;列表多子节点走updateChildren。
源码简化版:
// child:旧的Element;newWidget:新生成的Widget;newSlot:节点在父中的位置标记
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
// Case1:新Widget为null → 需要移除子节点
if (newWidget == null) {
if (child != null) {
deactivateChild(child); // 停用旧Element,脱离树,加入_inactiveElements
}
return null;
}
// newWidget不为null
if (child != null) {
// Case2:新旧Widget是同一个对象(const组件经常命中)
if (child.widget == newWidget) {
if (child.slot != newSlot) updateSlotForChild(child, newSlot);
return child; // 直接复用Element,**不调用update,子树完全跳过更新**
}
// Case3:canUpdate 判断:runtimeType + key同时相等,可以复用Element
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot) updateSlotForChild(child, newSlot);
child.update(newWidget); // 更新Element内部的widget引用
return child;
}
// Case4:不能复用,销毁旧Element
deactivateChild(child);
}
// Case5:child==null,或者上面不能复用 → 创建全新Element
return inflateWidget(newWidget, newSlot);
}
|------------------|--------------|--------------------|
| 条件 | 结果 | 说明 |
| 同类型 + 同 key | ✅ 复用 Element | 最常见,正常更新路径 |
| 同类型 + 不同 key 变了, | ❌ 不复用 key | 当作全新组件 |
| 不同类型 | ❌ 不复用 Widget | 种类变了,必须重建 |
| 同一实例 (identical) | ✅ 直接跳过 | const Widget 的最优路径 |
4.Element的挂载流程
mount():把新建的 Element 加入 Element树,完成三棵树初始化的核心入口。 调用起点:
inflateWidget;inflateWidget要么是runApp初始化,要么是updateChild中需要新建节点时调用。
前置:inflateWidget(创建+挂载的入口)
Element inflateWidget(Widget newWidget, Object? newSlot) {
// GlobalKey 优先从_inactiveElements复用旧Element
if(newWidget.key is GlobalKey){
final Element? reuseElem = _retakeInactiveElement(newWidget.key);
if(reuseElem!=null){
reuseElem._activateWithParent(this, newSlot);
return updateChild(reuseElem, newWidget, newSlot);
}
}
// 1. 通过Widget.createElement() 创建Element对象
final Element newChild = newWidget.createElement();
// 2. 执行mount,挂载到父Element,slot标记位置
newChild.mount(this, newSlot);
return newChild;
}
slot:标记该节点在父中的位置,Row/Column列表子节点靠slot记录顺序;单节点一般为null。
mount基础父类逻辑 Element.mount
void mount(Element? parent, Object? newSlot) {
_parent = parent; // 绑定父Element,加入Element树
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active; // 标记为激活状态
if(parent != null){
_owner = parent._owner; // 继承BuildOwner,管理脏节点
}
}
mount做两件基础事:绑定父节点、标记生命周期为active,子类再扩展各自逻辑。
Element有两大子类,ComponentElement、RenderObjectElement,mount逻辑完全不一样。
1.ComponentElement
这里ComponentElement包含(StatelessElement / StatefulElement,组合组件)。
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_firstBuild(); // 首次构建
}
void _firstBuild() {
rebuild(); // 调用performRebuild
}
@override
void performRebuild() {
Widget? builtWidget;
if(this is StatefulElement){
// StatefulElement:触发 initState → didChangeDependencies
builtWidget = state.build();
}else{
// StatelessElement:直接build
builtWidget = widget.build();
}
// 递归!调用updateChild 处理子Widget,继续inflateWidget+mount子Element
_child = updateChild(_child, builtWidget, null);
}
StatefulElement 额外关键点 mount过程中会实例化State对象:
StatefulElement构造函数执行state = widget.createState()- 赋值
state._element = this,State绑定在Element上,不是Widget - mount →
_firstBuild→rebuild→performRebuild - 执行
state.initState()(只mount阶段执行一次!) - 执行
state.didChangeDependencies() - 调用
state.build()获取子Widget updateChild递归挂载子节点
✅ Stateful生命周期顺序: createState() → mount() → initState() → didChangeDependencies() → build()
2.RenderObjectElement
这里的RenderObjectElement包含(Text、Image,真正渲染组件)。
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
// 1. 创建RenderObject对象,建立Element ↔ RenderObject映射
_renderObject = widget.createRenderObject(this);
// 2. 将RenderObject attach挂载进RenderObject渲染树
attachRenderObject(newSlot);
_dirty = false;
// 如果有子节点,继续updateChild递归子树
}
-
createRenderObject:由RenderObjectWidget实现,生成真正负责布局绘制的RenderObject; -
attachRenderObject:把_renderObject插入父RenderObject的子列表,Render树正式构建完成。
重点:只有RenderObjectElement才有RenderObject;ComponentElement没有RenderObject。
📌完整调用链路(runApp初始化一棵树)
runApp(rootWidget)
→ RenderObjectToWidgetAdapter.attachToRenderTree
→ rootWidget.createElement() 创建RootElement
→ rootElement.mount(null, null) 根节点mount,parent为null
→ ComponentElement.mount
→ _firstBuild() → rebuild() → performRebuild
→ build()拿到子widget
→ updateChild
→ inflateWidget
→ childWidget.createElement()
→ child.mount(parentElement, slot) 【递归】
├─ ComponentElement:继续_firstBuild/build/updateChild
└─ RenderObjectElement:createRenderObject + attachRenderObject
递归:父mount过程中,会完成全部子节点的createElement + mount,深度优先构建完整三棵树。
挂载完整时序(Stateful组件示例)
widget.createElement()→ new StatefulElement,内部执行widget.createState()实例化Stateelement.mount(parent, slot)- super.mount:绑定父、标记active
_firstBuild()→rebuild()→performRebuild()- 调用
state.initState()【仅mount执行一次】 - 调用
state.didChangeDependencies() - 调用
state.build()拿到子Widget updateChild()处理子Widget- 子节点走
inflateWidget→createElement→mount()(深度优先递归子树)
- 子节点走
- 如果是RenderObjectElement类型子节点:创建RenderObject + attachRenderObject加入渲染树。
深度优先:父mount,然后完整挂载所有子节点,子全部mount完毕,父mount才算完成。
mount阶段和update阶段的区别
| mount(挂载,新建节点) | update(更新,复用Element) | |
|---|---|---|
| 触发来源 | inflateWidget,第一次创建节点 | updateChild中canUpdate=true,复用Element |
| Stateful | 执行initState() | 不会执行initState,执行didUpdateWidget |
| RenderObject | 调用createRenderObject、attachRenderObject | 调用updateRenderObject,不重建RenderObject |
| _firstBuild | 执行 | 不会执行 |
initState只在mount挂载的时候执行一次;后续父rebuild复用Element,只会走update,不会再次initState。
3. RenderObject 树(渲染实体)
RenderObject 负责实际的布局和绘制计算。它拥有尺寸、位置等几何信息,并通过 paint() 方法调用 Skia 进行绘制。布局(Layout)和绘制(Paint)是渲染管线中最耗时的阶段。
渲染流程:构建 Widget 树 → 创建/更新 Element 树 → 创建/更新 RenderObject 树 → 布局(Layout) → 绘制(Paint) → 合成(Compositing) → 上屏(Rasterizing)。
1.RenderObject 的核心方法
|---------------------|------|----------------|
| 方法 | 阶段 | 职责 |
| performLayout() | 布局 | 计算自身尺寸 + 约束子节点 |
| paint() | 绘制 | 往 Canvas 上画内容 |
| hitTest() | 命中测试 | 判断点击是否落在自己区域内 |
| describeSemantics() | 无障碍 | 描述语义信息 |
| markNeedsLayout() | 标记 | 请求重新布局 |
| markNeedsPaint() | 标记 | 请求重新绘制 |
2.布局协议:约束向下,尺寸向上
Flutter 采用单向数据流布局模型:父节点向子节点传递约束(Constraints),子节点向父节
点返回尺寸(Size)。
3.自定义RenderObject 实战
通过CustomPaint + CustomPainter 可以介入绘制层:
Dart
class CirclePainter extends CustomPainter {
final Color color;
final double radius;
CirclePainter({required this.color, required this.radius});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
radius,
paint,
);
}
@override
bool shouldRepaint(CirclePainter old) {
return old.color != color || old.radius != radius;
}
}
// 使用:
CustomPaint(
size: Size(200, 200),
painter: CirclePainter(color: Colors.blue, radius: 50),
child: Center(child: Text('Hello')),
)
4.RenderObject的生命周期
Dart
// 1. Element mount 时创建
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this); // ← 创建
attachRenderObject(newSlot); // ← 挂到渲染树
}
// 2. Widget 更新时同步
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject!); // ← 更新
}
// 3. Element unmount 时销毁
void unmount() {
widget.didUnmountRenderObject(renderObject!); // ← 清理
super.unmount();
}