Flutter中从setState()到屏幕更新的完整流程
当在Flutter应用中调用setState()
时,会触发一系列精确的步骤,最终导致UI更新。以下是这个过程的详细分解,配合具体示例:
1. setState()调用与标记阶段
步骤详解:
- 调用
setState()
并执行其回调函数 - 标记当前
State
对象为"dirty" - 向引擎注册一个新帧的请求
源码层次的工作:
dart
@protected
void setState(VoidCallback fn) {
// 断言确保不在build过程中调用setState
assert(_debugLifecycleState != _StateLifecycle.defunct);
// 执行回调函数,通常是更新一些状态变量
fn();
// 核心:标记当前State需要重建
_element!.markNeedsBuild();
}
其中_element.markNeedsBuild()
会:
- 将Element标记为dirty
- 将Element添加到BuildOwner的_dirtyElements列表
- 调用
SchedulerBinding.instance.scheduleFrame()
请求新帧
例子 - 计数器状态更新:
dart
class CounterState extends State<Counter> {
int count = 0;
void incrementCounter() {
// 调用setState并提供匿名回调
setState(() {
count++; // 状态变更
print("状态已更新为: $count"); // 立即执行
});
// 此时Element已被标记为dirty,但屏幕尚未更新
print("setState已调用完毕,但屏幕尚未更新");
}
@override
Widget build(BuildContext context) {
print("build方法被调用,count = $count");
return Text('Count: $count');
}
}
当incrementCounter()
被调用时,输出顺序是:
ini
状态已更新为: 1
setState已调用完毕,但屏幕尚未更新
...等待引擎触发新帧...
build方法被调用,count = 1
2. 调度帧阶段
步骤详解:
scheduleFrame()
通知Flutter引擎有工作要做- 引擎会等待下一个vsync信号(通常是16.67ms,对应60fps)
- vsync信号到达时,引擎调用
handleBeginFrame
和handleDrawFrame
源码工作流程:
dart
void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled)
return;
// 通知引擎需要在下一个vsync信号处理新帧
window.scheduleFrame();
_hasScheduledFrame = true;
}
然后在vsync时:
dart
// 简化的流程
void _handleBeginFrame(Duration rawTimeStamp) {
// 处理动画和各种回调
}
void _handleDrawFrame() {
// 1. 运行所有微任务
// 2. 构建所有dirty Elements
// 3. 布局和绘制
buildOwner.buildScope(renderViewElement);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
// 4. 合成并发送到GPU
renderView.compositeFrame();
}
例子 - 动画状态更新:
dart
class AnimatedBoxState extends State<AnimatedBox> with SingleTickerProviderStateMixin {
late AnimationController controller;
double width = 100.0;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
)..addListener(() {
setState(() {
// 这会在每一帧被调用,随着动画值变化
width = 100.0 + controller.value * 100.0;
print("内部状态更新: width = $width");
});
});
controller.forward();
}
@override
Widget build(BuildContext context) {
print("绘制宽度为 $width 的box");
return Container(width: width, height: 100, color: Colors.blue);
}
}
这个例子中,动画控制器在每一帧触发setState:
- 动画开始,请求首帧
- vsync信号到达,运行动画tick
- 动画tick更新值并调用setState
- 标记Element为dirty
- 在同一帧内完成构建和渲染
- 重复步骤2-5直到动画完成
3. 构建阶段(Build Phase)
步骤详解:
BuildOwner
遍历所有dirty elements- 调用每个dirty element的
rebuild()
方法 - Element调用关联State的
build()
方法 - 与之前的Widget tree进行比较,更新Element树
源码关键部分:
dart
void buildScope(Element context) {
// ...
try {
// 按深度排序,确保父元素先于子元素重建
_dirtyElements.sort(Element._sort);
// 处理所有需要重建的元素
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
// 获取并重建Element
final Element element = _dirtyElements[index];
element.rebuild(); // 这里会调用widget.build()
// ...处理潜在新增的dirty elements
}
} finally {
// ...
}
}
例子 - 层级嵌套重建:
dart
class ParentWidget extends StatefulWidget {
@override
ParentWidgetState createState() => ParentWidgetState();
}
class ParentWidgetState extends State<ParentWidget> {
bool showDetails = false;
void toggleDetails() {
setState(() {
showDetails = !showDetails;
print("父级状态变更: showDetails = $showDetails");
});
}
@override
Widget build(BuildContext context) {
print("父级build开始");
return Column(
children: [
ElevatedButton(
onPressed: toggleDetails,
child: Text('Toggle Details'),
),
ChildWidget(showDetails: showDetails),
],
);
}
}
class ChildWidget extends StatefulWidget {
final bool showDetails;
ChildWidget({required this.showDetails});
@override
ChildWidgetState createState() => ChildWidgetState();
}
class ChildWidgetState extends State<ChildWidget> {
@override
Widget build(BuildContext context) {
print("子级build开始, showDetails=${widget.showDetails}");
return widget.showDetails
? Card(child: Text('详细信息...'))
: SizedBox.shrink();
}
}
当点击按钮时,重建顺序是:
ini
父级状态变更: showDetails = true
父级build开始
子级build开始, showDetails=true
关键流程是:
- 点击按钮触发
toggleDetails()
setState()
标记ParentWidgetState的Element为dirty- 下一帧开始构建阶段
- 按深度排序后先重建父Element
- 父Element的重建导致子Widget重新创建
- 子Element检测到Widget配置(showDetails)变化,并更新
4. 布局阶段(Layout Phase)
步骤详解:
- RenderObject树接收到Element的更新
- 标记需要重新布局的RenderObject
- 从上到下计算约束(constraints)
- 从下到上确定尺寸(sizes)
工作流程:
dart
void flushLayout() {
// 确保不在布局过程中再次触发布局
try {
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout.toList();
_nodesNeedingLayout.clear();
// 按深度排序,确保父节点先于子节点布局
dirtyNodes.sort((a, b) => a.depth - b.depth);
// 依次对每个节点进行布局
for (final RenderObject node in dirtyNodes) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
} finally {
// ...
}
}
例子 - 复杂布局计算:
dart
class ResponsiveContainer extends StatefulWidget {
@override
ResponsiveContainerState createState() => ResponsiveContainerState();
}
class ResponsiveContainerState extends State<ResponsiveContainer> {
bool useWideLayout = false;
void toggleLayout() {
setState(() {
useWideLayout = !useWideLayout;
print("布局状态变更: useWideLayout = $useWideLayout");
});
}
@override
Widget build(BuildContext context) {
print("构建响应式容器");
return Column(
children: [
ElevatedButton(
onPressed: toggleLayout,
child: Text('切换布局'),
),
useWideLayout
? Row(
children: [
Expanded(child: ColoredBox(color: Colors.red, child: SizedBox(height: 100))),
Expanded(child: ColoredBox(color: Colors.blue, child: SizedBox(height: 100))),
],
)
: Column(
children: [
ColoredBox(color: Colors.red, child: SizedBox(height: 100, width: double.infinity)),
ColoredBox(color: Colors.blue, child: SizedBox(height: 100, width: double.infinity)),
],
),
],
);
}
}
布局流程:
- 点击按钮更改
useWideLayout
并触发setState - 重建Widget树,现在结构从Column变为Row(或反之)
- Element树更新,创建/复用子Elements
- RenderObject树收到更新,标记需要重新布局
- 布局引擎从根部开始,向下传递约束:
- 父RenderObject向Row/Column传递约束
- Row/Column向其子项传递约束
- 子RenderObject确定自己的尺寸并向上报告:
- 颜色块根据约束确定尺寸
- Row/Column收集子项尺寸并确定自己的最终尺寸
5. 绘制阶段(Paint Phase)
步骤详解:
- 遍历标记为需要重绘的RenderObject
- 创建或更新绘制记录(Layer)
- 将绘制命令记录到Layer中
源码工作流程:
dart
void flushPaint() {
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint.toList();
_nodesNeedingPaint.clear();
// 按深度排序,确保父节点先于子节点绘制
dirtyNodes.sort((a, b) => a.depth - b.depth);
// 依次对每个节点进行绘制
for (final RenderObject node in dirtyNodes) {
if (node._needsPaint && node.owner == this) {
if (node._layer == null)
node._repaintBoundary = null;
node._paint();
}
}
} finally {
// ...
}
}
例子 - 自定义绘制与动画:
dart
class AnimatedCircleState extends State<AnimatedCircle> with SingleTickerProviderStateMixin {
late AnimationController _controller;
Color _color = Colors.blue;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
)..addListener(() {
setState(() {
// 空的setState,只是为了触发重绘
print("请求重绘,动画值: ${_controller.value}");
});
});
_controller.repeat(reverse: true);
}
void changeColor() {
setState(() {
_color = _color == Colors.blue ? Colors.red : Colors.blue;
print("颜色更改为: $_color");
});
}
@override
Widget build(BuildContext context) {
print("构建自定义绘制组件");
return Column(
children: [
ElevatedButton(
onPressed: changeColor,
child: Text('更改颜色'),
),
CustomPaint(
painter: CirclePainter(
color: _color,
progress: _controller.value,
),
size: Size(200, 200),
),
],
);
}
}
class CirclePainter extends CustomPainter {
final Color color;
final double progress;
CirclePainter({required this.color, required this.progress});
@override
void paint(Canvas canvas, Size size) {
print("执行paint方法, progress=$progress");
final center = Offset(size.width / 2, size.height / 2);
final radius = 50.0 + 30.0 * progress;
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
canvas.drawCircle(center, radius, paint);
}
@override
bool shouldRepaint(CirclePainter oldDelegate) {
return oldDelegate.color != color || oldDelegate.progress != progress;
}
}
绘制流程:
- 动画tick或按钮点击触发setState
- Element重建并更新RenderObject属性
- RenderObject标记为需要重绘
- 绘制阶段,CustomPaint的RenderObject调用CirclePainter的paint方法
- CirclePainter根据当前color和progress绘制圆形
- 绘制命令被收集到对应的Layer中
6. 合成与渲染阶段
步骤详解:
- 构建完整的Layer树
- 将Layer树提交给Flutter引擎
- 引擎将Layer树转换为Skia(GPU渲染库)命令
- GPU执行命令进行光栅化,产生像素
- 显示器在下一次刷新时显示这些像素
流程简化:
dart
// 在绘制完成后,Flutter框架调用以下方法
ui.SceneBuilder _sceneBuilder = ui.SceneBuilder();
// 每个Layer都会添加到场景中
void addToScene(ui.SceneBuilder builder) {
// ... Layer特定的添加逻辑
}
// 最后提交整个场景
ui.Scene scene = _sceneBuilder.build();
window.render(scene);
例子 - RepaintBoundary与Layer优化:
dart
class OptimizedUIState extends State<OptimizedUI> {
int topCounter = 0;
int bottomCounter = 0;
void incrementTop() {
setState(() {
topCounter++;
print("顶部计数器更新: $topCounter");
});
}
void incrementBottom() {
setState(() {
bottomCounter++;
print("底部计数器更新: $bottomCounter");
});
}
@override
Widget build(BuildContext context) {
print("主UI构建");
return Column(
children: [
ElevatedButton(
onPressed: incrementTop,
child: Text('更新顶部'),
),
// 使用RepaintBoundary创建独立的Layer
RepaintBoundary(
child: Builder(builder: (context) {
print("顶部区域构建: $topCounter");
return Container(
height: 100,
color: Colors.amber,
alignment: Alignment.center,
child: Text('顶部计数: $topCounter', style: TextStyle(fontSize: 24)),
);
}),
),
ElevatedButton(
onPressed: incrementBottom,
child: Text('更新底部'),
),
// 另一个独立Layer
RepaintBoundary(
child: Builder(builder: (context) {
print("底部区域构建: $bottomCounter");
return Container(
height: 100,
color: Colors.lightBlue,
alignment: Alignment.center,
child: Text('底部计数: $bottomCounter', style: TextStyle(fontSize: 24)),
);
}),
),
],
);
}
}
渲染优化流程:
-
点击"更新顶部"按钮时:
makefile顶部计数器更新: 1 主UI构建 顶部区域构建: 1 底部区域构建: 0
-
在绘制阶段:
- 两个RepaintBoundary各自创建独立的PictureLayer
- 顶部容器的绘制命令记录到第一个Layer
- 底部容器的绘制命令记录到第二个Layer
-
再次点击"更新顶部"按钮:
makefile顶部计数器更新: 2 主UI构建 顶部区域构建: 2 底部区域构建: 0
-
这次的绘制过程:
- 只有顶部的PictureLayer需要重绘
- 底部的Layer可以被复用,因为其内容没有变化
- 这就是Layer树的合成优化
-
最后,所有Layer组合成一个Scene:
- 根ContainerLayer包含所有子Layer
- Scene被提交给Flutter引擎
- 引擎将Scene转换为GPU命令
- 显示器在下一个vsync显示结果
7. 总结:完整流程图
从setState()
到屏幕更新的完整路径:
-
触发阶段:
setState()
被调用- 更新State中的数据
- 标记Element为dirty
- 请求新帧
-
调度阶段:
- 等待下一个vsync信号
- 处理开始帧回调
-
构建阶段:
- 遍历dirty elements
- 调用build()方法
- 更新Element树
-
布局阶段:
- 计算尺寸和位置
- 自上而下传递约束
- 自下而上确定尺寸
-
绘制阶段:
- 记录绘制命令
- 创建或更新Layer
-
合成与渲染阶段:
- 构建Layer树
- 提交给引擎
- GPU渲染
- 显示器展示
这个过程是Flutter实现流畅60fps动画的核心机制,通过精确控制每个阶段的工作,确保高效的UI渲染。