底层渲染原理¶
flutter修改UI,核心渲染:增量渲染,哪个widget变了就渲染谁。所以不变的定义常量const。
flutter没有图层,只有一层页面,整个页面是一个树。并不是全部都渲染,只渲染(新建)树中改变的节点widget。想要改变某一个控件的值,直接新创建一个widget对象控件,把原来的替换掉。
下面拆解 Widget、Element、RenderObject 三棵树 的协作机制,把“代码”变成“屏幕像素”的过程彻底透明化。
为什么是“三棵树”¶
Flutter 采用分层架构是为了极致性能。它通过“轻量级配置”驱动“重量级渲染”,实现了最小化更新。
- Widget:轻量级“设计图”,负责描述 UI,频繁重建成本极低。
- Element:核心“调度员”,负责管理生命周期和状态,决定是否复用。
- RenderObject:重量级“施工队”,真正负责布局和绘制,尽量复用。
三棵树的分工与协作¶
| 树 | 角色 | 职责 | 关键特性 |
|---|---|---|---|
| Widget Tree | 设计图 (Blueprint) | 描述 UI 长什么样(配置) | 不可变(Immutable),重建代价极低,善用 const |
| Element Tree | 施工队长 (Foreman) | 连接配置与实际渲染,持有 State | Diff 算法核心,决定复用还是重建 |
| RenderObject Tree | 施工队 (Worker) | 执行布局(Layout)和绘制(Paint) | 开销巨大,通过脏标记(Dirty)局部更新 |
协作流程(setState 背后):
- 触发:
setState()标记 Element 为dirty。 - Diff:下一帧,Element 对比新旧 Widget(看
runtimeType和Key)。 - 决策:匹配则复用 Element 和 RenderObject(仅更新配置);不匹配则销毁重建。
- 渲染:RenderObject 执行
layout和paint,提交给 GPU。
关键机制与性能优化¶
1. Element 的 Diff 与复用(性能基石)¶
- Key 的作用:在动态列表或排序场景中,
Key是 Element 复用的唯一标识。没有 Key 会导致状态错乱(如列表项状态“串位”)。 - 复用条件:新旧 Widget 的
runtimeType相同且Key匹配。
2. RenderObject 的脏标记机制¶
markNeedsLayout():尺寸/位置变了,触发重新布局+绘制。markNeedsPaint():仅颜色/透明度变了,只重绘,跳过布局(性能更好)。RepaintBoundary:将高频动画组件(如 Lottie)隔离为独立图层,避免其重绘导致整个页面重绘。
3. 状态(State)的真实归属¶
State 对象由 Element 持有,而非 Widget。Widget 只是不可变的配置描述。这就是为什么 Widget 重建后,State 中的数据依然存在(因为 Element 没换)。
实战避坑与最佳实践¶
| 误区 | 问题 | 解决方案 |
|---|---|---|
| 滥用 GlobalKey | 强制 Element 跨树迁移,破坏 Diff,性能极差 | 优先使用 ValueKey/ObjectKey |
| build() 里做重计算 | 每次 UI 刷新都重复计算,导致卡顿 | 缓存计算结果,或用 didUpdateWidget控制重算 |
| 忽视 const | 每次重建都创建新对象,增加 Diff 负担 | 对静态 Widget 加 const,编译期单例,零开销 |
| setState 范围过大 | 导致整棵子树重建 | 使用 ValueListenableBuilder或状态管理库局部刷新 |
调试工具(DevTools)观察三棵树¶
打开 Flutter DevTools → Widget Inspector,你可以实时看到:
- Widget Tree 面板:查看当前组件树结构
- 选中某个 Widget:右侧显示对应的 Element 信息(类型、Key、BuildContext)
- Performance Overlay:开启帧渲染监控,定位 RenderObject 的布局/绘制瓶颈
- Repaint Rainbow:开启后每次重绘区域变色,直观看出哪些区域在不必要地重绘
// main.dart 中开启 Repaint Rainbow(仅调试用)
import 'package:flutter/rendering.dart';
void main() {
debugRepaintRainbowEnabled = true; // 开启后重绘区域会闪烁彩色边框
runApp(MyApp());
}
💡 底层原理延伸¶
- BuildContext 本质:就是
Element对象本身,通过它可以在树中向上查找祖先(如Theme.of(context))。 - InheritedWidget:状态管理(Provider/Riverpod)的底层基础。利用 Element 树建立依赖关系,数据变更时精准通知依赖项重建。
总结:理解三棵树,你就理解了 Flutter 如何用声明式 UI 实现高性能渲染。掌握 Key、const、RepaintBoundary和局部刷新,是进阶开发的必备技能。
RenderObject¶
flutter渲染的是RenderObject树。与渲染有关的是Render树。
不是所有的Widget都会生成Render树,所以并不是所有的Widget都会显示到屏幕上。
Container继承StatelessWidget,StatelessWidget继承Widget,不会创建RenderObject对象。
只有继承RenderObject才会创建RenderObject。如布局Row、Column继承Flex,Flex继承RenderObjectWidget。Flex重写了父类的createRenderObject。
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key? key }) : super(key: key);
/// RenderObjectWidgets always inflate to a [RenderObjectElement] subclass.
@override
@factory
RenderObjectElement createElement();
@protected
@factory
/// 抽象方法,子类去实现
RenderObject createRenderObject(BuildContext context);
///省略代码
}
所有继承Widget的都会创建Element对象。Widget和Element一一对应。没有继承Render的也有createElement。
Element会调用mount方法。如果部件是继承RenderObjectWidget对象,RenderObjectElement的mount方法里面就会调用widget.createRenderObject方法。
Element把Widget和Render关联在一起。
Element相当于中间层。
Widget¶
StatelessWidget¶
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key? key }) : super(key: key);
@override
//创建element对象
StatelessElement createElement() => StatelessElement(this);//this就是widget
@protected
Widget build(BuildContext context);
}
调用StatelessWidget的build方法
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this);//调用StatelessWidget的build方法
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}
ComponentElement里面会调用mount方法,然后一层一层的调用,最终拿出widget对象调用build方法。
abstract class ComponentElement extends Element {
/// Creates an element that uses the given widget as its configuration.
ComponentElement(Widget widget) : super(widget);
Element? _child;
bool _debugDoingBuild = false;
@override
bool get debugDoingBuild => _debugDoingBuild;
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_lifecycleState == _ElementLifecycle.active);
_firstBuild();
assert(_child != null);
}
void _firstBuild() {
rebuild();
}
@override
@pragma('vm:notify-debugger-on-exception')
void performRebuild() {
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
/// Subclasses should override this function to actually call the appropriate
/// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
/// their widget.
@protected
Widget build();
/// 省略代码
}
Element
abstract class Element extends DiagnosticableTree implements BuildContext {
/// Creates an element that uses the given widget as its configuration.
///
/// Typically called by an override of [Widget.createElement].
Element(Widget widget)
: assert(widget != null),
_widget = widget;//_widget变量赋值
/// 省略代码
}
创建widget会调用createElement方法创建element,element创建完之后会调用外面widget的build,并把element作为context传出去。
总结:
statelessWidget会创建Element- Element创建会调用mount方法
- mount方法会调用widget的build方法进行渲染,并且将Element自己传出去
(BuildContext context)。
StatefulWidget¶
widget和state共用一个element
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key? key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);//this就是widget
@protected
@factory
State createState(); // ignore: no_logic_in_create_state, this is the original sin
}
class StatefulElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
/// 创建element对象,创建的时候多了一个创建State并把state保存
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
/// assert
state._element = this;//widget和state公用一个element
state._widget = widget;//把widget保存到state的_widget
assert(state._debugLifecycleState == _StateLifecycle.created);
}
@override
Widget build() => state.build(this);//state的build this是element
/// 省略代码
}
总结:
在Flutter渲染的流程中,有三棵树,Flutter引擎渲染是针对Render树中的对象进行渲染。
每一个widget创建出来都会创建一个Element对象
调用createElement方法。Element加入Element树中,都会调用mount方法。
- RanderElement继承RenderObjectElement继承Element
主要创建RenderObject对象,通过mount方法创建RenderObject对象。
-
StatefulElement继承ComponentElement
-
调用createState方法,创建state
- 将Widget赋值给State对象
-
调用state的build方法,并且将自己(Element)传出去
-
StatelessElement继承ComponentElement
主要调用build方法,并且将自己(Element)传出去
深色模式¶
Theme.of(context)依赖于context的位置。如果context不在正确的BuildContext层级中(例如,不在MaterialApp或Theme的子树中),Theme.of(context)将无法正确获取当前主题。