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

举报
bug菌 发表于 2025/12/25 12:04:18 2025/12/25
【摘要】 🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8 🌟前言:我先替你的手机“喊冤”两句🤣很多同学做 ArkUI 的 L...

🏆本文收录于「滚雪球学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 三个要点(背下来不亏)

  1. key 必须稳定且唯一(推荐用 id)
  2. item 组件要尽量轻量
  3. 配合分页 / 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-

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。