Flutter UI 绘制原理浅析

Flutter 的核心思想是:自己接管渲染,不依赖平台原生控件。平台只负责提供画布、Vsync 信号、输入事件和生命周期,Flutter 自己完成构建、布局、绘制、合成和光栅化。它通过自研渲染引擎(Skia 或 Impeller)直接在 Canvas 上绘制每一个像素,因此能做到跨平台高度一致,并尽量避免原生桥接带来的性能损耗。

整条链路可以概括为:

text 复制代码
Widget 树 → Element 树 → RenderObject 树 → Layer 树 → Scene → Engine 光栅化 → GPU/平台合成 → 屏幕

更本质地说,Flutter UI 绘制是:

声明式配置 → 增量式布局/绘制 → GPU 直接光栅化。


一、核心架构:三棵树模型

Flutter 将 UI 的构建、布局和绘制解耦为三棵独立的树。这是理解绘制原理的基础。

职责 特点 类比
Widget Tree 描述 UI 的配置 不可变、轻量级,只是配置蓝图 图纸
Element Tree Widget 的实例化上下文,管理生命周期 连接 Widget 与 RenderObject,负责 Diff 和更新调度 桥梁/仓库管理员
RenderObject Tree 负责实际布局和绘制 可变,存储几何信息,参与命中测试 施工队

1. Widget:不可变的"配置单"

Widget 只是 UI 的配置描述,不是真实控件。比如 TextContainerRow 都只是配置。它不可变、创建销毁成本很低,所以重建 Widget 本身并不昂贵。

dart 复制代码
Text('hello', style: ...)

Widget 只说明"我要什么",不负责"怎么画"。

2. Element:Widget 的实例化载体

Element 是 Widget 在树中的实例,持有生命周期、状态和 BuildContext。它负责:

  • 根据 Widget 创建或更新 RenderObject
  • 判断新旧 Widget 能否复用:比较 runtimeTypekey
  • 管理 StatefulWidget 的 State
  • 作为 Widget 和 RenderObject 之间的桥梁

BuildContext 的本质就是 Element。context.findRenderObject() 就是顺着 Element 树找到对应的 RenderObject。

只有 RenderObjectWidget 对应的 Element 才会创建 RenderObject。像 StatelessWidgetStatefulWidget 这类组合 Widget 没有自己的 RenderObject。

当调用 setState() 时,只会重建对应的 Widget 子树,然后 Element 通过 canUpdate 决定是复用旧 RenderObject,还是替换它。这就是 Flutter 高效更新的关键之一。

3. RenderObject:真正干活的"施工队"

RenderObject 才是负责布局、绘制、命中测试的对象。常见类型:

  • RenderBox:盒模型布局
  • RenderSliver:滚动布局
  • RenderView:整棵渲染树的根

它持有:

  • Constraints:父节点传下来的约束
  • size:自己计算出的大小
  • ParentData:父节点用来摆放子节点的数据,比如 offset

三棵树的关系可以记成:

text 复制代码
Widget       配置,不可变
Element      实例,管理生命周期和状态
RenderObject 布局、绘制、命中测试

更新时,Element 会尽量复用旧 RenderObject,只更新变化属性,而不是整棵树重建。


二、一帧的调度流程

当调用 setState、动画 tick、手势导致状态变化时,Flutter 不会立刻绘制,而是标记脏区域,并请求下一帧。

大致流程:

text 复制代码
setState / 动画 / 手势
  ↓
markNeedsBuild / markNeedsLayout / markNeedsPaint
  ↓
scheduleFrame
  ↓
等待 Vsync
  ↓
handleBeginFrame
  ↓
执行动画回调 Ticker
  ↓
handleDrawFrame
  ↓
build → layout → compositing bits → paint → composite → semantics
  ↓
生成 Scene
  ↓
提交给 Engine
  ↓
Raster 线程光栅化
  ↓
GPU / 平台合成上屏

核心调度类:

  • SchedulerBinding:帧调度
  • BuildOwner:管理需要重建的 Element
  • PipelineOwner:管理布局、绘制、合成、语义

handleDrawFrame 中会依次执行:

  1. buildScope:重建 dirty Element
  2. flushLayout:布局
  3. flushCompositingBits:更新合成位
  4. flushPaint:绘制,生成 Layer 树
  5. compositeFrame:生成 Scene 并提交 Engine
  6. flushSemantics:构建语义树

三、Build 阶段:生成和更新配置

setState 会调用 markNeedsBuild,把 Element 加入 BuildOwner 的 dirty 列表。

下一帧 buildScope 时:

  • 从 dirty Element 开始重建
  • 通过 Widget.canUpdate 判断能否复用 Element
  • 如果可复用,调用 Element.update
  • 如果不可复用,卸载旧 Element,创建新 Element

Build 阶段只生成或更新 Widget 和 Element 树,不进行真正布局和绘制。它会通过 Diff 算法对比新旧 Widget,标记需要更新的 RenderObject,比如 markNeedsLayoutmarkNeedsPaint

优化点:

  • const 减少重建
  • 拆分 Widget,缩小重建范围
  • 状态尽量下放,避免根节点 setState

四、Layout 阶段:约束向下,尺寸向上

布局由 PipelineOwner.flushLayout 触发,从 relayoutBoundary 开始。

核心规则:

Constraints 向下传递,Size 向上传递,父节点决定子节点位置。

RenderBox 为例:

text 复制代码
父传给子:BoxConstraints(minWidth, maxWidth, minHeight, maxHeight)
子计算后返回:Size
父根据 ParentData 设置子的 offset

父节点调用:

dart 复制代码
child.layout(constraints)

意思是:"最多给你 300 宽,自己看着办。"子节点在约束范围内决定自己的大小,并向上报告 Size。

RenderObject.layout 大致逻辑:

dart 复制代码
layout(constraints, parentUsesSize: true) {
  if (sizedByParent) {
    performResize();
  } else {
    performLayout();
  }
}

关键概念:

  • parentUsesSize:父是否依赖子的大小
  • relayoutBoundary:重布局边界,约束不变时,子重布局不影响父
  • markNeedsLayout:向上找到最近的 relayoutBoundary,加入待布局列表

约束有 4 个值:min/max Width、min/max Height。子节点不允许超出父约束,否则会报 overflow。布局通常是一趟向下、一趟向上的过程,采用单向数据流,避免循环依赖,保证 O(N) 线性复杂度。

某些 IntrinsicWidthIntrinsicHeight 会导致多次布局,应尽量避免。


五、Paint 阶段:生成 Layer 树

flushPaint 会从 RepaintBoundary 对应的 RenderObject 开始,调用:

dart 复制代码
void paint(PaintingContext context, Offset offset)

注意:这里的 paint 不是直接画到 GPU ,而是把绘制指令记录到 PictureLayer 中的 Picture

PaintingContext 提供:

  • canvas:记录绘制命令,如 drawRectdrawPathdrawImage
  • paintChild:递归绘制子节点
  • pushLayer:创建合成层

RenderObject 不直接画图,而是向 Layer Tree 提交绘制指令,比如 drawRectdrawImagepushTransform。Layer Tree 是一个保留模式(Retained Mode)的结构,用于后续合成。

常见 Layer:

  • PictureLayer:保存绘制指令
  • OffsetLayer:偏移层,RepaintBoundary 常用
  • TransformLayer:变换
  • ClipRectLayer / ClipRRectLayer / ClipPathLayer:裁剪
  • OpacityLayer:透明度
  • ContainerLayer:子层容器
  • PlatformViewLayer / TextureLayer:原生视图或纹理

RepaintBoundary 原理

RepaintBoundary 会创建一个独立的 OffsetLayer,其子树有独立的 PictureLayer

当子树重绘时,只重新记录该子树的 Picture,不影响其他层。Engine 可以复用其他层的缓存纹理。

所以频繁动画、滚动列表项常用 RepaintBoundary 隔离重绘。


六、Composite、Scene 与 Rasterize

Layer 树构建完成后,RenderView.compositeFrame 会通过 SceneBuilder 把 Layer 树转成 Scene

简化代码:

dart 复制代码
final sceneBuilder = SceneBuilder();
layer.buildScene(sceneBuilder);
final scene = sceneBuilder.build();
window.render(scene);

Compositing 阶段会把 Layer Tree 合成为 Scene,处理透明度、裁剪、变换等图层叠加。此时 UI 线程的工作结束,Layer 树被提交到 Engine。

Rasterize 光栅化阶段由 GPU 后端完成。Engine 层负责真正把 Scene 变成像素:

  1. Raster 线程接收 Scene
  2. 遍历 Layer 树
  3. 生成 GPU 绘制命令
  4. 光栅化到纹理
  5. 合成到 Surface
  6. 平台合成器上屏

Flutter 使用两种主要渲染后端:

  • Skia:传统 2D 图形库,运行时编译 shader,可能造成首次卡顿
  • Impeller:新渲染引擎,预编译 shader,使用 Metal/Vulkan,减少 shader 编译 jank

平台侧:

  • Android:SurfaceView / TextureView,最终由 SurfaceFlinger 合成
  • iOS:CAMetalLayer,最终由 CoreAnimation / 系统合成

Flutter 的 Surface 只是平台合成器中的一个层,平台会把 Flutter 画面和其他原生 UI 一起合成到屏幕。

VSync 同步确保帧率稳定(60fps/120fps),防止撕裂。


七、渲染引擎演进:Skia vs Impeller

特性 Skia(旧默认) Impeller(新默认)
Shader 编译 运行时编译,首次运行复杂动画可能掉帧 构建时预编译,消除运行时 Shader 编译卡顿
API 支持 OpenGL ES / Vulkan / Metal Metal(iOS)、Vulkan(Android)、DirectX(Windows)
状态 成熟稳定,逐步退役 iOS 已默认启用,Android 快速推进
性能 良好,但存在长尾卡顿 更稳定的帧时间,更好的并行化

截至 2025-2026 年,Impeller 在 iOS 上已是默认引擎,Android 端也在快速推进中。如果遇到首帧卡顿问题,升级到最新 Flutter 并确认 Impeller 已启用通常能解决。


八、高效更新机制:Relayout Boundary 与 Repaint Boundary

Flutter 并非每帧都重算整棵树。它引入了 Relayout Boundary 概念:

  • 当某个 RenderObject 的尺寸变化不影响父节点布局时,该节点成为 Relayout Boundary。
  • 布局脏标记(dirty)不会向上冒泡超过这个边界。
  • 局部更新,比如列表项内容变化,只会触发子树的 Layout/Paint,大幅减少计算量。

RepaintBoundary 则用于隔离重绘:

  • 将某些子树提升为独立 Layer
  • 避免 paint 阶段整树重绘
  • 动画、滚动列表项常用

二者共同构成 Flutter 增量更新的基础。


九、Platform Channel 与纹理层

对于视频、地图等原生内容,Flutter 使用 TextureLayer

  • 原生视图渲染到共享纹理,如 Android 的 SurfaceTexture、iOS 的 CVPixelBuffer
  • Flutter 将该纹理 ID 嵌入 Layer Tree
  • 由 GPU 直接合成

这样避免了 CPU 内存拷贝,实现零成本混合渲染。Platform Channel 负责与原生侧通信和控制,纹理层负责把原生内容嵌入 Flutter 渲染管线。


十、事件与命中测试

用户点击时,GestureBinding 收到 PointerEvent,从 RenderView 开始向下做 HitTest:

text 复制代码
RenderView.hitTest
  → 子节点 hitTest
  → 考虑 Transform、Clip、Opacity
  → 得到 HitTestResult
  → 分发给手势识别器

命中测试和布局、绘制都依赖 RenderObject 树。


十一、线程模型

Flutter 常见线程:

  • UI Thread:Dart 代码,执行 build、layout、paint,生成 Layer 树
  • Raster Thread:光栅化,生成 GPU 纹理
  • GPU Thread:提交 GPU 命令
  • Platform Thread:平台事件、生命周期

UI Thread 和 Raster Thread 分离,所以 UI 卡顿和光栅化卡顿是两类问题。


十二、性能优化地图

对应绘制链路,可以从以下方面优化:

  • 减少 Build:const、拆分 Widget、状态最小化
  • 减少 Layout:避免频繁改变约束,避免 Intrinsic
  • 减少 Paint:RepaintBoundaryCustomPainter.shouldRepaint
  • 减少 Raster:图片尺寸合适、缓存、避免 saveLayer
  • 列表优化:ListView.builderitemExtent
  • 动画优化:用 AnimatedBuilder 局部刷新,必要时加 RepaintBoundary
  • 布局约束尽量简单,避免多层 Column 嵌套 Expanded 导致多次 layout
  • 使用 DevTools Performance / Timeline 定位 UI 或 Raster 瓶颈

调试绘制性能时,推荐使用 Flutter DevTools 中的:

  • Performance Overlay
  • Layout Explorer
  • Repaint Rainbow

它们可以可视化三棵树的状态和重绘区域。


十三、总结

Flutter UI 绘制的核心链路是:

text 复制代码
Widget 配置
  → Element 实例
  → RenderObject 布局/绘制
  → Layer 树
  → Scene
  → Engine 光栅化
  → GPU/平台合成
  → 屏幕

其中:

  • Build 决定 UI 长什么样
  • Layout 决定每个节点多大、在哪
  • Paint 记录绘制指令到 Layer
  • Composite 组织 Layer 并生成 Scene
  • Engine 把 Scene 光栅化成像素并上屏

这种设计带来了三个核心优势:

  1. 跨平台一致性:不依赖 OEM 控件,像素级可控。
  2. 高性能:跳过原生桥接,直接操作 GPU;增量更新减少无效计算。
  3. 高帧率保障:自管渲染管线,可精确控制每一帧的耗时预算。

一句话总结:

Widget 描述"要什么",Element 负责"怎么变",RenderObject 负责"怎么画";布局靠约束向下、尺寸向上,绘制靠 Canvas 指令记录到 Layer,合成成 Scene,最后由 GPU 光栅化上屏。

相关推荐
yume_sibai5 小时前
01-Flutter入门指南
flutter
lqj_本人16 小时前
Flutter 三方库「flutter_ble_peripheral」的鸿蒙化适配指南
flutter·华为·harmonyos
lqj_本人17 小时前
Flutter 三方库「flutter-dualscreen」的鸿蒙化适配指南
flutter·华为·harmonyos
●VON20 小时前
鸿蒙跨平台框架怎么选?从真实需求比较 Flutter、React Native、KMP/CMP 与 Web 路线
flutter·react native·harmonyos
lqj_本人21 小时前
Flutter 三方库「flutter_displaymode」的鸿蒙化适配指南
flutter·华为·harmonyos
lqj_本人1 天前
Flutter 三方库「drag_and_drop_flutter」的鸿蒙化适配指南
flutter·华为·harmonyos
天空之城--1 天前
Flutter 三棵树:原理、机制与最佳实践
flutter
天空之城--1 天前
Flutter线程模型完全指南:从架构到实战
flutter·架构
天空之城--1 天前
Flutter底层原理深度解析
flutter