news 2026/4/16 10:18:09

Flutter 性能优化:卡顿掉帧的堆叠卡片列表优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter 性能优化:卡顿掉帧的堆叠卡片列表优化

最近在做一个堆叠式卡片列表(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设计师喜欢使用大量的高斯模糊、圆角阴影这些性能杀手,被逼无奈),欢迎各位大佬提意见,合适的会积极采纳的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/11 15:39:27

AI工具收藏家的秘密:Open Interpreter等20个神器,云端随时切换

AI工具收藏家住的秘密&#xff1a;Open Interpreter等20个神器&#xff0c;云端随时切换 你是不是也和我一样&#xff0c;看到新的AI工具上线就忍不住想试一试&#xff1f;从自动写代码的Open Interpreter&#xff0c;到一键生成艺术图的Stable Diffusion WebUI&#xff0c;再…

作者头像 李华
网站建设 2026/4/12 20:40:21

DeepSeek-R1-Distill-Qwen-1.5B技巧:处理长文本的方法

DeepSeek-R1-Distill-Qwen-1.5B技巧&#xff1a;处理长文本的方法 1. DeepSeek-R1-Distill-Qwen-1.5B模型介绍 DeepSeek-R1-Distill-Qwen-1.5B是DeepSeek团队基于Qwen2.5-Math-1.5B基础模型&#xff0c;通过知识蒸馏技术融合R1架构优势打造的轻量化版本。其核心设计目标在于实…

作者头像 李华
网站建设 2026/4/13 20:25:55

万物识别镜像中文标签自定义方法,扩展你的识别类别

万物识别镜像中文标签自定义方法&#xff0c;扩展你的识别类别 在实际项目中&#xff0c;通用的物体识别模型虽然能覆盖大量常见类别&#xff0c;但往往难以满足特定业务场景下的精细化分类需求。例如&#xff0c;在零售场景中需要识别“可口可乐”和“百事可乐”&#xff0c;…

作者头像 李华
网站建设 2026/4/13 5:55:49

入门必看:Keil5如何正确显示中文注释(图文说明)

Keil5中文注释乱码&#xff1f;一招搞定&#xff0c;从此告别方块问号&#xff01;你是不是也遇到过这种情况&#xff1a;辛辛苦苦写了一段带中文注释的代码&#xff0c;结果在Keil5里打开一看——满屏“□□□”或者“”&#xff0c;注释全变“天书”&#xff1f;别急&#xf…

作者头像 李华
网站建设 2026/4/14 1:34:13

SenseVoice Small性能优化:提升批量处理效率

SenseVoice Small性能优化&#xff1a;提升批量处理效率 1. 引言 1.1 业务场景描述 在语音识别与情感分析的实际应用中&#xff0c;SenseVoice Small模型因其轻量化设计和多语言支持能力&#xff0c;被广泛应用于智能客服、会议记录、情感监测等场景。由开发者“科哥”基于F…

作者头像 李华
网站建设 2026/4/5 16:25:36

bert-base-chinese负载均衡:高并发应对方案

bert-base-chinese负载均衡&#xff1a;高并发应对方案 1. 背景与挑战 随着自然语言处理技术在工业场景中的广泛应用&#xff0c;基于预训练模型的服务部署正面临日益增长的访问压力。bert-base-chinese 作为中文 NLP 领域最基础且广泛使用的预训练模型之一&#xff0c;常被用…

作者头像 李华