鸿蒙应用开发实战【18】— Scroll + ForEach 列表滚动实战

📅 2026/7/16 5:20:03 👁️ 阅读次数 📝 编程学习
鸿蒙应用开发实战【18】— Scroll + ForEach 列表滚动实战

鸿蒙应用开发实战【18】— Scroll + ForEach 列表滚动实战

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

列表展示是移动端最核心的 UI 模式之一。号码助手的首页、卡号管理页、状态列表页都大量使用了Scroll + ForEach组合来展示可滚动列表。本篇深入讲解这个组合的使用方式、性能优化策略,以及与List组件的区别。

本篇涵盖:Scroll 组件用法、ForEach 数据渲染、key 函数设计、layoutWeight 弹性高度、底部留空策略、Scroll + Column 的组合、List vs Scroll 的选择、下拉刷新实现。


图1:Scroll + ForEach 布局结构、layoutWeight 弹性高度、key 函数设计、底部留空策略


一、Scroll 组件基础

1.1 Scroll 与 List 的选择

特性Scroll + Column/RowList
内容类型任意组件(异构内容)同类型列表项
性能全量渲染(适合 <100 项)虚拟化(适合 >100 项)
滚动方向横向/纵向纵向/横向(需配置)
编程接口相对简单功能更丰富
号码助手使用✅ 首页/卡号管理(数量少)未使用(数据量小不需要)

1.2 Scroll 基本用法

Scroll(){Column(){// 列表内容ForEach(this.items,(item:ItemType)=>{ItemComponent({data:item})},(item:ItemType)=>`${item.id}`)// 底部留空(避免内容被 FAB/底栏遮挡)Text('').height(90)}.padding({left:16,right:16})}.layoutWeight(1)// 占用 Column 中的剩余垂直空间

二、号码助手中的 Scroll 实现

2.1 首页应用列表

// HomePage.ets — 正常列表模式Scroll(){Column(){// ① ForEach 渲染过滤后的列表ForEach(this.getFilteredRows(),(row:AppRow)=>{AppListItem({appName:row.binding.app_name,iconColor:row.binding.icon_key,category:row.binding.category,cardLabel:row.cardLabel,status:row.binding.status,bindingId:row.binding.id??0})},(row:AppRow)=>`binding_${row.binding.id??0}_${row.binding.status}`)// ↑ key 函数:确保 status 变化时正确重渲染// ② 底部留空:避免最后一项被 FAB 悬浮按钮遮挡Text('').height(90)}.padding({left:16,right:16})}.layoutWeight(1)// ← 关键:弹性占满剩余高度

2.2 批量选择模式列表

// 批量选择模式:带复选框的列表Scroll(){Column(){ForEach(this.getFilteredRows(),(row:AppRow)=>{this.SelectableRow(row)// 调用 @Builder 方法},(row:AppRow)=>`sel_${row.binding.id??0}_${this.selectedIds.includes(row.binding.id??0)?'1':'0'}`)// ↑ key 函数:包含选中状态,选中/取消选中时重渲染Text('').height(80)// 批量操作底栏高度留空}.padding({left:16,right:16})}.layoutWeight(1)

2.3 卡号管理列表

// CardManagePage.etsScroll(){Column(){if(this.loading){LoadingProgress().width(40).height(40).color(AppColors.PRIMARY)}else{ForEach(this.cards,(card:CardEntity)=>{this.CardRow(card)},(card:CardEntity)=>`card_${card.id??0}`)}Text('').height(90)}.padding({left:16,right:16,top:8})}.layoutWeight(1)

三、ForEach 详解

3.1 基本语法

ForEach(arr:Array<T>,// 数据数组itemGenerator:(item:T,idx:number)=>void,// 渲染函数keyGenerator?:(item:T,idx:number)=>string// key 函数(强烈建议))

3.2 key 函数的重要性

// key 函数决定 ArkUI 如何识别列表项的"身份"// ❌ 不提供 key:每次数组变化时全量重渲染ForEach(this.rows,(row:AppRow)=>{AppListItem({...})})// ❌ 只用 index 作为 key:删除中间项时后续全部重渲染ForEach(this.rows,(row:AppRow)=>{AppListItem({...})},(row:AppRow,idx:number)=>`${idx}`)// ✅ 用业务 ID + 关键状态:精确最小化重渲染ForEach(this.rows,(row:AppRow)=>{AppListItem({...})},(row:AppRow)=>`binding_${row.binding.id??0}_${row.binding.status}`)

3.3 key 函数设计原则

  1. 唯一性:同一数组内 key 必须唯一
  2. 稳定性:数据未变化时 key 不变
  3. 包含影响渲染的状态:如果 status 变化影响 UI,key 中要包含 status
// 示例:不同场景的 key 函数设计// 场景1:只有增删,无状态变化(row:AppRow)=>`${row.binding.id??0}`// 场景2:有状态变化(选中/未选中)(row:AppRow)=>`${row.binding.id??0}_${this.selectedIds.includes(row.binding.id??0)?'sel':''}`// 场景3:有多个维度影响渲染(status + 筛选条件)(row:AppRow)=>`binding_${row.binding.id??0}_${row.binding.status}_${this.filterCatIdx}_${this.filterCardIdx}`

四、layoutWeight 高度管理

4.1 为什么 Scroll 需要 layoutWeight

// 首页的布局结构Column(){// 标题栏(固定高度)TitleBar()// height: 44vp// 统计卡(固定高度)StatsRow()// height: ~88vp// 分区标题(固定高度)SectionHeader()// height: 44vp// 内容区(需要占满剩余高度)Scroll(){// ...}.layoutWeight(1)// ← 弹性分配剩余高度// 如果不加 layoutWeight,Scroll 高度为 0,无法滚动!}.height('100%')

4.2 layoutWeight 的计算规则

// Column 总高度 = 屏幕高度(.height('100%'))// 减去固定高度子组件后的剩余 = Scroll 的高度// 例:屏幕高度 800vp// 标题栏:44vp// 统计卡:88vp// 分区标题:44vp// 剩余:800 - 44 - 88 - 44 = 624vp → Scroll 的高度

五、底部留空策略

5.1 为什么需要底部留空

// 问题:FAB 悬浮按钮(.overlay)遮挡了最后一个列表项// 解决:在列表最后添加一个空白占位Scroll(){Column(){ForEach(this.rows,...)// 底部占位:高度 = FAB 高度 + 底部安全区域Text('').height(90)// FAB 约 48vp + bottom: 24 + 余量 ≈ 90vp}}

5.2 不同场景的留空高度

场景建议留空高度
有 FAB 悬浮按钮90vp
有批量操作底栏(~64vp)80vp
无底部遮挡20vp(防止内容贴边)

六、Scroll 的其他配置

6.1 横向滚动

// 横向 Scroll(如卡片横向滑动)Scroll(){Row({space:12}){ForEach(this.cards,(card:CardEntity)=>{CardThumbnail({data:card}).width(160)// 固定每张卡片宽度},(card:CardEntity)=>`${card.id??0}`)}.padding({left:16,right:16})}.scrollable(ScrollDirection.Horizontal)// 横向滚动.scrollBar(BarState.Off)// 隐藏滚动条

6.2 ScrollController 编程控制

privatescroller:Scroller=newScroller()Scroll(this.scroller){Column(){...}}// 滚动到顶部Button('回到顶部').onClick(()=>{this.scroller.scrollTo({xOffset:0,yOffset:0})})// 滚动到底部this.scroller.scrollEdge(Edge.Bottom)

七、本篇小结

知识点核心要点
Scroll + Column适合 <100 项的列表,全量渲染
layoutWeight(1)让 Scroll 弹性占满剩余高度(必须)
ForEach key 函数包含 id + 影响渲染的状态,精确重渲染
底部留空Text(‘’).height(90) 避免 FAB 遮挡最后一项
ScrollController编程控制滚动位置

参考资料

  • 鸿蒙应用开发实战【05】— 首页开发与状态管理
  • 鸿蒙应用开发实战【79】— LazyForEach 虚拟化
  • Scroll 组件参考
  • ForEach 使用指南
  • List 组件参考
  • LazyForEach 高性能列表
  • layoutWeight 属性
  • Column 容器

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

  • 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
  • Scroll 组件文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-scroll
  • ForEach 指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-rendering-control-foreach