Flutter 性能优化:卡顿掉帧的堆叠卡片列表优化
2026/3/17 23:14:38 网站建设 项目流程

最近在做一个堆叠式卡片列表(Stack Card List)。效果是挺好看的,卡片层层堆叠,吸顶效果也很丝滑。

但是,当数据量一上来(比如超过 100 条),就开始掉帧。在我的测试机上,滑动起来会有一点明显的卡顿,DevTools 里的火焰图也是,特别是光栅线程,绝大部分耗时超过了最低标准16ms很多。

经过一顿排查和优化,成功把 Raster(GPU)耗时从16ms+降到了4ms左右,实现了60fps/120fps满帧运行。

现在来复盘一下,我是如何一步步“拯救”这个页面的。


第一个问题是为什么会卡?

首先,我的布局不是普通的 ListView,而是为了实现堆叠视差效果,用 Stack + Positioned + for循环手写出来的。

通过 Flutter DevTools 分析,我发现了几个首要目标:

  1. 逻辑层:全量渲染。原因是Stack 不像 ListView 自带懒加载(Recycling)。如果我有 100 条数据,它就会傻傻地把 100 个 Widget 全部创建并绘制出来,哪怕底下的 90 个都被压住看不见,导致了极大的没有必要的性能消耗。

  2. 渲染层:GPU 也就是 Raster 线程爆炸。一开始因为要先赶完成度,导致对与渲染性能没有太在意,每一张卡片都加了BoxShadow(高斯模糊)。当多张卡片叠在一起,GPU 就要对这块区域的像素重复计算多次模糊卷积,直接算崩了。

  3. 图层层:Layer 太多。为了优化重绘,我之前给每个卡片包了 RepaintBoundary,结果导致图层过多,合成开销反而变大了。


第一步:逻辑层优化 —— 手动实现“滑动窗口”

既然 Stack 不会自动回收不可见的元素,那我就手动编写修改为只画看得到的部分,避免全部渲染出来导致性能浪费。

修改前:
循环遍历所有数据,生成 Widget。GPU 实际上在画整个长列表,哪怕它们被堆叠在最底下。

修改后:
引入了一个滑动窗口的概念。在 for 循环里加了两个判断关卡:

  1. 剔除顶部:算出当前滚动位置大约对应第几个卡片。如果某个卡片被压在堆叠深处(比如第 0-10 个),直接 continue 跳过,不渲染。

  2. 限制数量:设置一个 maxRealRenderCount(比如 10)。屏幕上最多只允许出现 10 个卡片,超出的直接 break。

// 伪代码逻辑 for (int i = 0; i < tasks.length; i++) { // 1. 如果被压在最底下看不见 -> 跳过 if (visualIndex < firstRenderIndex) continue; // 2. 如果屏幕上已经画了够多了 -> 停止 if (renderedCount > 10) break; // ... 正常的渲染逻辑 }

效果:
无论数据源有 100 条还是 1000 条,系统永远只处理10 个Widget。性能复杂度从 O(N) 降到了 O(1)


第二步:渲染层优化 —— LOD (细节层次) 阴影降级

这是对 GPU 减负最大的一步。

修改前:
所有卡片,无论是否选中,都带着 blurRadius: 20 的高斯模糊。需要注意的是,Blur 是性能杀手,计算量是指教级增长的。

修改后:
我采用了一种LOD (Level of Detail)策略:

  • 选中态:保持设计稿的高质量阴影,怎么华丽怎么来。

  • 列表态:彻底关掉模糊(blurRadius: 0),改用 spreadRadius(扩散)或者 1.0 的微模糊来模拟阴影。

boxShadow: [ if (isSelected) // 选中时:奢华模糊 BoxShadow(color: Colors.black26, blurRadius: 25, offset: Offset(0, 10)) else // 列表时:极速模式 (几乎0成本) BoxShadow(color: Colors.black12, blurRadius: 1.0, spreadRadius: 0) ]

效果:
Raster 线程耗时瞬间从红色变回了蓝色。因为对于 GPU 来说,画硬边矩形比画高斯模糊快了几百倍。


第三步:图层与构建优化

这一步的目的是把 CPU 和显存的开销也降下来。

  1. 移除 RepaintBoundary
    之前给每个 Item 包这个是为了隔离重绘,但在整体滚动的列表中,这导致了显存碎片化。去掉后,整个列表合并为一个大图层,GPU 合成更轻松。

  2. 优化 AnimatedContainer
    卡片有缩放动画。为了防止动画过程中文字内容反复 Rebuild,我把内容提取出来,作为 child 传给 AnimatedContainer。
    原理:Flutter 发现 child 引用没变,就会复用之前的布局,只重画背景色和形状。这让 UI 线程(CPU)几乎不耗时。


最终成果

经过这几步的优化:

  • 逻辑上:剔除了 90% 的无效渲染。

  • 图形上:降低了 99% 的像素计算量。

  • 结构上:减少了图层和 Widget 重建。

结果:

  • UI 线程耗时:< 2ms

  • Raster 线程耗时:< 5ms

  • 帧率:稳稳的 60fps / 120fps

下面是优化前后的火焰图对比:

优化前:

优化后:

最近也是刚开始接触性能优化这方面(主要是新来的Ui设计师喜欢使用大量的高斯模糊、圆角阴影这些性能杀手,被逼无奈),欢迎各位大佬提意见,合适的会积极采纳的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询