你写的 List 为啥越滑越卡?真的是手机不行,还是你“喂”得太猛了?

🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
🌟前言:我先替你的手机“喊冤”两句🤣
很多同学做 ArkUI 的 List / Grid,一上来就豪气冲天:
“我有 3000 条数据,我就要一次性全渲染!”
然后滑着滑着:掉帧、发热、内存飙升、页面像在“喘气”😮💨。
讲真,手机此刻在想:**你礼貌吗?**😂
ArkUI 的 List/Grid 本来就是为大数据量场景设计的,只要你用对方式(懒加载 + 轻量组件 + 合理布局),它是真的能很丝滑的。来,咱把它整明白!🚀
📚目录导航(本节:列表与网格组件 List / Grid)🧭
- 🧩 ListItem / ItemGroup:列表的基础积木
- 🧺 Grid / 水流布局:瀑布流怎么写才不翻车
- 💤 懒加载核心:LazyForEach 的正确打开方式
- 🖐️ 滑动事件:onScroll / onReachEnd / 滚动联动玩法
- ⚡ 性能优化:让你从“卡顿制造者”变“丝滑工程师”😎
- 🧪 实战 Demo:List + 分组 + 懒加载 + 滚动监听(带代码)
🧩1) ListItem / ItemGroup:别把 List 当成“大号 Column”😅
在 ArkUI 里,List 是专门为可滚动长列表设计的容器,它和 Column 的根本区别是:
List更适合大数据量 + 滚动- 内部配合 懒加载 才能省内存、不卡顿
ListItem是它的基本单元,ItemGroup负责“分组结构”
✅最小结构(先让你看清骨架)
@Entry
@Component
struct SimpleListDemo {
private data: string[] = Array.from({ length: 30 }, (_, i) => `Item ${i}`);
build() {
List() {
LazyForEach(this.data, (item: string) => {
ListItem() {
Text(`📌 ${item}`).fontSize(18).padding(16)
}
}, (item: string) => item) // key 生成器:稳定很重要!
}
}
}
😏吐槽一句:
如果你 LazyForEach 的 key 不稳定,系统会以为“这批 item 全换人了”,然后疯狂重建组件,卡到你怀疑人生。
🧱2) ItemGroup:分组列表的“官方姿势”📚
分组列表常见于:通讯录、设置项分类、订单按日期分组……
ItemGroup 的思路是:一个组 = 一个标题 + 多个 ListItem。
@Entry
@Component
struct GroupListDemo {
private groups: Record<string, string[]> = {
'🔥 热门': ['A', 'B', 'C'],
'🧊 冷门': ['D', 'E']
};
build() {
List() {
ForEach(Object.keys(this.groups), (groupName: string) => {
ItemGroup({ header: this.buildHeader(groupName) }) {
ForEach(this.groups[groupName], (item: string) => {
ListItem() {
Text(`➡️ ${item}`).padding(14)
}
}, (item: string) => item)
}
}, (k: string) => k)
}
}
@Builder
buildHeader(title: string) {
Text(title).fontSize(16).fontWeight(FontWeight.Bold).padding(12)
}
}
✅小建议:分组内部如果数据也很多,
ForEach建议换LazyForEach,否则还是会“堆”出来。
🧺3) Grid 与水流布局:瀑布流不是“随便两列”就完事了🌊
网格布局常见于:相册、商品列表、短视频封面。
ArkUI 的 Grid 适合规则网格;瀑布流(水流布局)则需要更灵活的高度策略。
✅规则 Grid 示例:两列卡片
@Entry
@Component
struct GridDemo {
private items: number[] = Array.from({ length: 50 }, (_, i) => i);
build() {
Grid() {
LazyForEach(this.items, (i: number) => {
GridItem() {
Column() {
Text(`🧩 #${i}`).fontSize(16)
Text('商品描述...').fontSize(12).opacity(0.6)
}
.padding(12)
.borderRadius(12)
}
}, (i: number) => i.toString())
}
.columnsTemplate('1fr 1fr') // 两列
.rowsGap(10)
.columnsGap(10)
.padding(12)
}
}
🌊水流布局(瀑布流)怎么理解?
瀑布流的核心是:每个卡片高度不同,系统根据列高度自动“往最短的一列塞”。
不同版本 ArkUI 对瀑布流支持方式可能略有差异(有的直接提供 WaterFlow 组件,有的用 Grid + 动态高度模拟)。
你只需要抓住原则:
- item 的高度不要固定死
- 图片要懒加载/占位
- key 稳定 + 数据分页加载
⚠️瀑布流最常见翻车点:
- 图片一加载就触发重排,导致滚动抖动
- 组件太复杂,GPU/CPU 压力大
- 一次性加载大量图片导致内存爆炸💣
💤4) 懒加载(LazyForEach):丝滑的核心开关🔑
一句话:
LazyForEach 让“只渲染屏幕上看得见的内容”成为可能。
✅LazyForEach 三个要点(背下来不亏)
- key 必须稳定且唯一(推荐用 id)
- item 组件要尽量轻量
- 配合分页 / onReachEnd 做增量加载
✅正确 key 示例:用 id
type Row = { id: string; title: string };
private rows: Row[] = Array.from({ length: 100 }, (_, i) => ({
id: `id_${i}`,
title: `Row ${i}`
}));
List() {
LazyForEach(this.rows, (row: Row) => {
ListItem() { Text(row.title).padding(16) }
}, (row: Row) => row.id) // ✅稳定 key
}
😭不要这样干:
(row) => Math.random().toString()
你这是在逼系统每一帧都“重新做人”。
🖐️5) 滑动事件:滚动监听怎么玩才不伤性能🧠
常用需求:
- 滚动时显示/隐藏顶部栏
- 上拉加载更多
- 记录阅读位置
- 滚动联动动画
✅常见事件思路
onScroll:滚动过程中高频触发(⚠️慎用重逻辑)onScrollIndex:索引变化时触发(更轻量)onReachStart/onReachEnd:到顶/到底触发(最适合做分页)
😎经验:
onScroll 里别做 setState + 大计算,否则你会用代码亲手把帧率掐死。
⚡6) 性能优化:让 List/Grid 真正“飞起来”的实战清单🚀
这部分我给你一份“工程上真能救命”的清单👇
✅渲染层面
- 🧱 item 结构别太深:减少嵌套层级
- 🖼️ 图片用占位 + 延迟加载:避免滚动时抖动
- 🧩 避免在 item 里创建大量临时对象(比如每次 build 都 new)
- 🧷 样式复用:常用 padding/fontSize 抽成常量
✅状态管理
- 🔥 别把整个列表数据都做成频繁变化的状态
- 🎯 只更新变动行:避免全量刷新
- 🧠 把滚动事件做节流(throttle)或只在关键节点更新 UI
✅数据层面
- 📄 分页加载:一次 20~50 条更友好
- 🧹 离屏数据可回收:别永远无限 append
- 🧭 key 稳定:减少重建和 diff 成本
🧪7) 综合实战 Demo:List + ItemGroup + LazyForEach + 滚动到底分页📦
下面这段是我很推荐你拿去直接改的“通用模板”——实战味儿很浓😄
type Item = { id: string; title: string; group: string };
@Entry
@Component
struct ListBestPracticeDemo {
// 模拟分页数据源
private page: number = 0;
private pageSize: number = 20;
// 用状态存数据(实际项目可接入 ViewModel)
@State private items: Item[] = [];
private groups: string[] = ['🔥 热门', '⭐ 推荐', '🧊 冷门'];
aboutToAppear() {
this.loadMore();
}
private loadMore() {
const start = this.page * this.pageSize;
const newItems: Item[] = Array.from({ length: this.pageSize }, (_, i) => {
const index = start + i;
return {
id: `id_${index}`,
title: `Item ${index}`,
group: this.groups[index % this.groups.length]
};
});
this.items = this.items.concat(newItems);
this.page++;
console.info(`✅ loaded page=${this.page}, total=${this.items.length}`);
}
build() {
List() {
// 分组渲染:每组使用 LazyForEach(避免堆满)
ForEach(this.groups, (g: string) => {
ItemGroup({ header: this.headerBuilder(g) }) {
const groupItems = this.items.filter(it => it.group === g);
LazyForEach(groupItems, (it: Item) => {
ListItem() {
Row() {
Text('📌').fontSize(18).margin({ right: 8 })
Text(it.title).fontSize(16)
}
.padding(14)
}
}, (it: Item) => it.id)
}
}, (g: string) => g)
}
.onReachEnd(() => {
console.info('⬇️ reach end, load more...');
this.loadMore();
})
}
@Builder
headerBuilder(title: string) {
Row() {
Text(title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
}
.padding(12)
}
}
✅你可以直接把
loadMore()替换成真实接口请求;
✅把filter优化成“按组维护列表”会更高效(大量数据时更明显);
✅图片卡片场景,把Row()换成Column()+ Image 占位即可。
🎯本节小结:一句话让你记住 List/Grid 的“灵魂”🧠
- ListItem/ItemGroup:结构清晰,分组别乱套
- Grid/瀑布流:高度可变 + 图片懒加载是关键
- LazyForEach:稳定 key = 性能生命线
- 滑动事件:少做重逻辑,避免频繁状态更新
- 性能优化:轻组件、少嵌套、分页加载、资源控制
🧧福利赠与你🧧
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」专栏(全网一个名),bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。
✨️ Who am I?
我是bug菌(全网一个名),CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主/价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-
- 点赞
- 收藏
- 关注作者
评论(0)