前言:
在实际开发中,我们常听到:"Flutter 没有复用"、"item 滑出又重建了"、"initState 怎么又调用了?"
那么:
- Flutter 到底有没有"复用"?
- ListView 渲染到底做了什么?
- 如何优化 ListView 的滑动性能?
🏗️ 一、ListView 渲染整体架构图
md
ListView
└── SliverList / SliverChildBuilderDelegate
└── itemBuilder(index) ➜ Widget
└── createElement()
└── mount() ➜ RenderObject ➜ Layer ➜ GPU
每个 item 构建并非无限保留,而是有生命周期的。
👀 二、你以为的"复用" VS 实际的渲染机制
❌ 误解:
"Flutter 和原生一样,滑动时 item 会被缓存、复用。"
✅ 真相:
Flutter 并没有复用 Widget 或 View 对象,而是做了更轻量级的结构销毁与重建,这是因为:
- Flutter 是声明式 UI;
- UI 结构是随时可以销毁 + 重建的;
ListView.builder
利用了Sliver + Delegate机制做到了:
只构建屏幕可见 + 边界预加载区域的 Widget,滑出后即销毁。
🔁 三、Flutter 怎么实现"复用"视觉效果的?
在滑动 ListView
时,我们有个直观感受是:
item 离开屏幕后,新的 item 快速出现,像是"复用"了前面的 item。
但 Flutter 并没有像 Android 那样有 ViewHolder 复用池。
那么它是怎么实现视觉上"复用"的呢?这里是关键逻辑:
✅ 1. 只构建"需要"的部分
ListView.builder
会只 build 屏幕可见区域 + cacheExtent 范围的 item;- 滑出视口范围的 Element 被标记为
inactive
,下一帧就会被 unmount 销毁; - 这个"懒构建"机制避免了构建太多 widget 的消耗。
✅ 2. Widget diff 算法提升了构建效率
- Flutter 的
Element.update()
方法会比较新旧 Widget:
dart
if (widget.runtimeType == oldWidget.runtimeType &&
widget.key == oldWidget.key) {
// 复用旧的 Element 和 RenderObject,只更新内容
} else {
// 销毁旧的 Element,重新构建
}
- 所以 只要 key 和类型不变,Flutter 会走更新逻辑而不是销毁重建;
- 这就像 Virtual DOM 的 diff,极大降低了性能开销。
✅ 3. RenderObject 尽量保持
- 在
StatefulWidget
中,Element
的_state._renderObject
会尽可能复用; - Flutter 框架避免在滑动过程中频繁创建新的 RenderObject;
- GPU 层的 Layer、Picture 也通过缓存避免重复绘制。
⚠️ 四、Flutter 为什么不直接搞"View复用池"?
Flutter 是声明式 UI 框架,不适合维护复杂的对象池逻辑,原因是:
- Widget 是轻量不可变的,没必要复用;
- Element + RenderObject 已具备可控生命周期;
- 显式"复用池"反而会让代码变复杂,违背 declarative UI 理念。
📐 五、与原生 Android / iOS 的对比
对比点 | Flutter ListView | Android RecyclerView | iOS UITableView |
---|---|---|---|
渲染方式 | Skia 图层 + 重建 UI 结构 | ViewHolder + 视图缓存池 | Cell + 重用队列 |
复用机制 | 重建 Widget、State、Element | 缓存 ViewHolder 复用视图对象 | 从复用队列中取出 Cell |
控制粒度 | Flutter 管 Widget rebuild | 可精准控制每个 item 生命周期 | 自动复用 Cell,提高效率 |
保持状态 | 需手动 keepAlive 或 PageStorage | View 持久存在,状态天然保留 | 同上,Cell 未销毁可保状态 |
滚动性能 | 构建轻、但频繁销毁构建 | 滚动快,依赖 GC 控制内存 | 滚动快,易出现"闪白"问题 |
状态保留 | 默认销毁,需配置才保留 | 默认保留 | 默认保留 |
✅ 六、小结
方面 | Flutter ListView 的特点 |
---|---|
✅ 优点 | 构建简单,无需手动管理复用,配合 Builder 高效 |
⚠️ 注意 | 默认 item 会销毁,不会自动保留状态 |
💡 提示 | 想要类似原生复用效果,要配合 keepAlive 或 cacheExtent 等 |