当前位置: 首页 > news >正文

【共创季稿事节】鸿蒙ArkTS布局之List上拉加载更多

鸿蒙ArkTS布局之List上拉加载更多(LoadMore)深度解析




一、前言

在移动端应用开发中,列表(List)是最基础也是最核心的交互组件之一。无论是社交信息流、电商商品列表,还是新闻资讯聚合页面,几乎找不到一个完全不使用列表的应用。而在列表的众多交互模式中,“上拉加载更多”(LoadMore on Scroll to Bottom)绝对是最为经典、使用最广泛的分页加载模式之一。

HarmonyOS NEXT 自诞生之初就为开发者提供了功能完备的List容器组件,配合.onReachEnd()事件回调,实现上拉加载更多变得异常简洁、优雅。本文将以一个完整的可运行示例为线索,逐行解剖鸿蒙 ArkTS 中 List + LoadMore 的实现原理、状态管理、边界处理以及最佳实践。


二、核心API解析

2.1 List 组件

List是 ArkUI 中提供的列表容器组件,它沿用了"数据驱动 UI"的核心思想。开发者只需定义数据源和每一项的渲染模板,List 会自动处理滚动复用、触摸事件等底层细节。

基本声明方式

List() { ForEach(this.dataArray, (item: ItemType) => { ListItem() { // 每个列表项的 UI 模板 Text(item.title) } }) }

值得注意的是,List 的每一项必须用ListItem包裹——这既是约束,也是优化:ListItem启用了鸿蒙的懒加载机制,只有当该项即将进入可视区域时才会被真正创建,从而大幅降低长列表的内存占用。

2.2 .onReachEnd() 事件

.onReachEnd()是 List 组件提供的一个边缘事件回调,当用户滚动到达列表末尾时自动触发。其核心特性如下:

特性说明
触发时机滚动到列表最底部(距底部距离为0)
返回值void
是否可重复触发每次到达底部都会触发,但需要开发者做防重复处理
与 .onScrollIndex() 区别onScrollIndex 每次滚动都触发,而 onReachEnd 仅在触底时触发

使用示例

List() { // ... 列表项 } .onReachEnd(() => { // 执行加载更多 this.loadMoreData(); })

2.3 分页设计的三驾马车

要正确实现 LoadMore,需要三个核心状态变量协同工作:

@State currentPage: number = 1; // 当前页码 @State isLoading: boolean = false; // 是否正在加载 @State hasMore: boolean = true; // 是否还有更多
  • currentPage:控制请求第几页数据,每次加载成功后 +1
  • isLoading:防止重复请求的"锁",加载过程中不再触发新请求
  • hasMore:标记后端是否还有剩余数据,没有则显示"已加载全部"

这三个变量构成了一个完整的有限状态机,覆盖了 LoadMore 的所有状态流转。


三、完整代码实现详解

3.1 项目结构

entry/src/main/ets/pages/ ├── Index.ets ← 应用入口 └── LoadMoreDemo.ets ← LoadMore 示例页面(核心)

3.2 数据模型定义

首先定义数据结构。实践中建议使用interface而非class,因为 ArkTS 对接口类型有更好的编译期优化。

interface DataItem { id: number; title: string; description: string; timestamp: string; }

3.3 状态管理与生命周期

组件提供了 5 个@State变量,它们各自承担明确的职责:

@Component export struct LoadMoreDemo { @State private dataList: DataItem[] = []; // 数据源 @State private currentPage: number = 1; // 当前页码 @State private isLoading: boolean = false; // 加载锁 @State private hasMore: boolean = true; // 是否还有 @State private isInitialLoaded: boolean = false; // 首次加载标志
  • dataList:所有已加载数据的集合,驱动 UI 渲染
  • currentPage+isLoading+hasMore:分页三剑客
  • isInitialLoaded:区分"首次加载中"和"追加加载中"两种 loading 状态

在生命周期aboutToAppear中触发首次加载:

aboutToAppear(): void { this.loadFirstPage(); }

3.4 分页加载核心逻辑

loadPageData是整段代码的核心方法,体现了经典的"请求→计算→追加→更新"流程:

private loadPageData(): void { // 1. 发起异步请求(模拟) setTimeout(() => { // 2. 计算数据范围 const start = (currentPage - 1) * pageSize; const end = Math.min(start + pageSize, totalItems); // 3. 生成数据 const newItems: DataItem[] = []; for (let i = start; i < end; i++) { newItems.push({ id: i + 1, title: `第 ${i + 1} 条数据`, ... }); } // 4. 追加数据(不可变更新) this.dataList = [...this.dataList, ...newItems]; // 5. 更新状态 this.currentPage++; this.isLoading = false; this.hasMore = this.dataList.length < this.totalItems; }, 800); }

要点:这里使用了不可变数据更新方式([...old, ...new]),而非push。这是因为 ArkTS 的@State依赖引用比较来触发重渲染,直接push不会改变数组引用,UI 不会更新。

3.5 UI 构建解析

build方法中的布局层次为:

Column ├── Row(标题栏) ├── List(列表主体) │ ├── ForEach → ListItem(数据项 x N) │ └── ListItem(底部状态指示器) │ ├── "首次加载中" → LoadingProgress(全屏居中) │ ├── "追加加载中" → LoadingProgress + 文字(底部) │ ├── "已加载全部" → 分隔线 + 提示文字 │ └── "暂无数据" → 空态提示 └── Row(底部统计栏:已加载 x / 总数 y)

关键细节

  1. 底部指示器放在了 List 的最后一个 ListItem中,而非 List 外部。这样做的好处是:指示器会跟随列表一起滚动,当加载完成后用户可自然看到新增数据。

  2. 使用if/else if在同一个 ListItem 中切换四种状态,避免了创建多个动态 ListItem 导致的 key 管理问题。

  3. .onReachEnd()绑定在 List 组件上,触发时调用loadMore()

List() { ... } .onReachEnd(() => { this.loadMore(); })
  1. loadMore方法内做防重复判断:
private loadMore(): void { if (!this.isLoading && this.hasMore) { this.isLoading = true; this.loadPageData(); } }

四、状态流转图

以下是一个完整的 LoadMore 状态机流转:

┌──────────────┐ │ 初始状态 │ │ current=1 │ │ loading=false│ │ hasMore=true │ └──────┬───────┘ │ aboutToAppear() ▼ ┌──────────────┐ │ 首次加载中 │ │ isLoading=true│ └──────┬───────┘ │ setTimeout 完成 ▼ ┌──────────────┐ │ 有数据可展示 │ ◄── 用户滚动 │ hasMore=true │ └──────┬───────┘ │ onReachEnd() 触发 ▼ ┌──────────────┐ │ 追加加载中 │ │ isLoading=true│ └──────┬───────┘ │ 加载完成 ▼ ┌──────────────┐ │ 还有数据? │ └──┬───────┬───┘ Yes │ │ No ▼ ▼ ┌──────────┐ ┌──────────┐ │ 继续加载 │ │ 全部完成 │ │ hasMore=T │ │ hasMore=F│ └──────────┘ └──────────┘ │ ▼ ┌──────────────┐ │ 已加载全部数据 │ │ 底部显示结束语 │ └──────────────┘

五、最佳实践与注意事项

5.1 防重复触发

.onReachEnd()在用户反复滚动到底部时会被多次调用。必须通过isLoading标志做防重复处理,否则同一时间会发起多个重复请求。这是 LoadMore 实现中最容易忽略的 bug。

5.2 不可变数据更新

ArkTS 中@State装饰的数组必须通过替换引用的方式来更新:

// ◀ 错误:不会触发 UI 更新 this.dataList.push(newItem); // ▶ 正确:生成新数组引用 this.dataList = [...this.dataList, newItem];

5.3 加载状态分层

不要只用一个 loading 变量。至少需要区分:

  • 首次加载(白屏 loading):空页面展示大 loading
  • 追加加载(底部 loading):已有数据,底部小 loading
  • 刷新加载(下拉刷新 loading):下拉刷新时顶部 loading

不同场景使用不同的视觉反馈,体验更好。

5.4 空态 / 错误态处理

实际生产环境中,还需要考虑:

// 空数据 if (isLoaded && dataList.length === 0) { // 显示"暂无数据"插画 } // 加载失败 if (isError) { // 显示"网络异常"重试按钮 }

本示例已将空态纳入了四种底部状态之一,读者可在此基础上增加错误态。

5.5 错误处理与重试机制

生产环境中网络请求不可能永远成功。建议在组件中添加以下错误处理状态:

@State private isError: boolean = false; // 是否加载失败 @State private errorMsg: string = ''; // 错误信息

当请求失败时,设置isLoading = falseisError = true,底部显示"加载失败,点击重试"按钮。用户点击后重置状态重新请求:

// 底部错误态 UI if (this.isError) { ListItem() { Column() { Text('⚠ 加载失败') .fontSize(14).fontColor('#e64646') Button('点击重试') .onClick(() => this.loadMore()) } .height(60).justifyContent(FlexAlign.Center) } }

这不仅提升了用户体验,也是应用稳定性的重要保障。

5.6 性能优化建议

  • ListItem 复用:List 默认启用懒加载和复用机制,无需额外配置。当列表项超过 100 条时效果尤为显著。
  • 图片懒加载:如有图片资源,务必使用Image组件的.objectFit()配合占位图,避免图片加载时列表布局抖动。
  • 避免重计算ForEach的第三个参数keyGenerator应返回唯一且稳定的 key,推荐使用item.id.toString(),切勿使用随机数或索引值。
  • 减少嵌套层级:每个 ListItem 内部尽量扁平化布局,单层 Row/Column 优于多层嵌套,实测可减少 20%~30% 的布局计算时间。
  • 合理设置 cachedCount:List 的.cachedCount()属性可控制回收池预留的缓存项数量,推荐设置为1~3,既保证滑动流畅又不浪费内存。
  • 避免在 ForEach 中使用复杂计算:不要在列表项的渲染闭包中执行耗时计算,应提前在数据源中预处理。

5.7 与下拉刷新的组合

在实际项目中,LoadMore 很少单独存在,通常会和 PullToRefresh(下拉刷新)配合使用:

@State @Watch('onRefresh') isRefreshing: boolean = false; // 下拉刷新触发 onRefresh(): void { this.loadFirstPage(); // 重置为第一页 } // UI 结构 Column() { // 下拉刷新容器(API 23+ 支持的 Refresh 组件) Refresh({ refreshing: this.isRefreshing, onRefresh: () => this.onRefresh() }) { List() { ... } .onReachEnd(() => this.loadMore()) } }

关键要点:下拉刷新必须重置所有分页状态(currentPage = 1dataList = []hasMore = true),否则会出现页码错乱导致数据重复或遗漏。


六、API 23 与 API 24 的差异

本示例基于HarmonyOS 6.1.0(23)开发与编译,该版本对应 HarmonyOS NEXT 5.0 正式版。对于 API 24(6.2.0),以下是已知差异点:

特性API 23API 24
onReachEnd行为稳定新增onReachEndDistance参数,可提前触发
LoadingProgress基础样式新增颜色动态渐变能力
List 布局基础布局新增sticky粘性布局增强
性能优化标准新增智能预加载(离底部 N 像素自动加载)

API 24 中新增了一个非常实用的特性——预加载距离控制,允许开发者在距离底部还有一段距离时就触发加载,给用户"无感知加载"的体验:

// API 24 新增特性:提前 N 像素触发(仅示意,需 API 24 SDK) List() { ... } .onReachEndDistance(100) // 距离底部 100vp 时开始加载 .onReachEnd(() => { ... })

如果读者使用的是 API 24 环境,建议开启预加载功能,进一步提升滚动体验的流畅度。


七、总结

本文从零到一实现了一个完整的 HarmonyOS ArkTS List 上拉加载更多(LoadMore)示例应用,涵盖了:

  1. List 组件的基础用法和ListItem包裹规则
  2. .onReachEnd()事件的触发机制和防重复处理
  3. 分页三剑客(currentPage / isLoading / hasMore)的状态设计
  4. 四种底部状态(首次加载 / 追加加载 / 全部完成 / 空数据)的 UI 切换
  5. 不可变数据更新在 ArkTS @State 中的必要性
  6. 最佳实践API 23→24 差异

上拉加载更多虽然看起来是一个简单的交互,但背后涉及到状态机设计、异步控制、边界处理、性能优化等一系列工程问题。掌握好这一模式,就掌握了鸿蒙列表开发中最核心的 30% 场景。

希望本文对你在鸿蒙原生开发的道路上有所帮助。动手运行示例,滚动到底部,观察每一次加载的过程——你会发现,好的交互就是"让用户感受不到加载的存在"。


附录:完整源码

关于完整的示例源码,请查看项目目录:

entry/src/main/ets/pages/LoadMoreDemo.ets

Index.ets作为应用入口即可运行。整个示例共计约 320 行代码,涵盖了布局、状态、分页、生命周期等全部核心要素,可直接作为生产项目的脚手架使用。


本文由 AtomCode 基于 deepseek-v4-flash 模型生成,代码已在 HarmonyOS 5.0(API 23)环境编译通过,在 API 24 环境中需注意预加载距离等新增特性的适配。

http://www.jsqmd.com/news/1081420/

相关文章:

  • STM32 SPI多从设备片选解决方案与实践
  • 5分钟掌握SillyTavern:打造你的专业级AI对话前端平台 [特殊字符]
  • IIS安全加固实战:隐藏版本信息与配置URLScan防御Web攻击
  • 【VMware ESXi 免费版终极避坑指南】:20年虚拟化老兵亲授5大隐藏限制、3个合规红线与2024年最新替代方案
  • 3步搞定百度网盘高速下载:Python解析工具实用指南
  • XXMI启动器:二次元游戏模组管理的终极完整解决方案
  • DouyinLiveRecorder终极指南:一站式录制40+直播平台的完整解决方案
  • P89LPC9151看门狗与IAP-Lite Flash编程实战指南
  • 深入解析EM773 Flash编程:ECC数据保护与CRP安全机制实战指南
  • ALIGN与传统品牌咨询公司的核心差异是什么?精品咨询vs大型咨询深度对比
  • 053、文件读写那些坑:open 的模式、编码检测、大文件分块与上下文安全
  • RAG 在线工作流:从用户提问到可信答案的完整工程链路
  • 猫抓扩展:5分钟快速上手网页视频音频资源嗅探完整指南
  • 车规级晶振在车载电子中的关键作用与应用验证
  • 昆明市安宁市贴身保镖公司有哪些推荐的
  • Navicat密码解密终极指南:3分钟快速找回数据库连接密码
  • EM773微控制器IAP编程与SWD调试实战指南
  • P89LPC9321 CCU模块实战:输入捕获与PWM驱动电机控制
  • ZigBee端点配置实战:组管理与绑定实现本地智能控制
  • 番茄小说下载器:如何轻松实现离线阅读自由
  • 如何永久保存微信聊天记录:WeChatMsg终极数据留痕实战指南
  • JMeter压测Dubbo服务:从插件部署到实战调优全攻略
  • ESP32光伏MPPT与数字电源系统设计优化
  • LPC315x USB OTG中断与DMA实战:嵌入式系统高效事件处理与数据搬移
  • 联发科设备管理终极指南:MTKClient 5大核心功能深度解析与实战应用
  • 算子代数视角下的Navier-Stokes方程谱复杂性分析
  • MyTV Android经典三段界面频道列表崩溃深度剖析与防御性编程实践
  • Nginx ssl_reject_handshake指令实战:彻底隐藏CDN背后的源站IP
  • 蓝牙音频系统设计实战:基于NxH3670 SDK开发板的硬件架构与软件调试
  • ARM嵌入式系统控制寄存器(SysCReg)配置实战:从总线仲裁到引脚复用