HarmonyOS应用开发实战萌宠日记 - 首页下拉刷新与骨架屏设计前言在萌宠日记的首页中当前版本采用静态数据展示但在实际生产环境中数据需要动态加载和刷新。下拉刷新和骨架屏是提升数据加载体验的两项关键技术下拉刷新让用户主动获取最新数据骨架屏在数据加载期间提供视觉反馈避免白屏闪烁。本文将从萌宠日记的首页出发深入解析下拉刷新组件的集成、骨架屏的设计与实现以及数据加载状态管理的完整方案。一、下拉刷新组件1.1 Refresh 组件HarmonyOS 提供了Refresh 组件来实现下拉刷新功能// Refresh 组件的基本用法 Refresh({ refreshing: this.isRefreshing, onRefresh: this.onRefresh }) { Scroll() { Column({ space: 16 }) { // 首页内容... } } }1.2 Refresh 组件属性属性类型说明refreshingboolean是否正在刷新onRefresh() void触发刷新时的回调offsetnumber下拉触发刷新的阈值showTimeboolean是否显示刷新时间1.3 萌宠日记的刷新实现// HomePage.ets — 下拉刷新完整实现 Entry Component export struct HomePage { State isRefreshing: boolean false State lastRefreshTime: string build() { Refresh({ refreshing: this.isRefreshing, onRefresh: this.onRefresh }) { Scroll() { Column({ space: 16 }) { // 1. 标题栏 this.HeaderRow() // 2. 宠物信息卡片 this.PetProfileCard() // 3. 快捷操作 this.QuickActionRow() // 4. 今日记录 this.TodayRecordSection() // 5. 健康提醒 this.HealthReminderSection() } } .width(100%) .height(100%) .backgroundColor(#FFF8F0) .scrollBar(BarState.Off) } .onStateChange((refreshStatus: RefreshStatus) { console.log(Refresh status:, refreshStatus) }) } onRefresh(): void { this.isRefreshing true // 模拟网络请求 setTimeout(() { this.loadHomeData() this.isRefreshing false this.lastRefreshTime this.getCurrentTime() }, 1500) } loadHomeData(): void { // 重新加载首页数据 console.log(Home data refreshed) } getCurrentTime(): string { const now new Date() return ${now.getHours()}:${now.getMinutes()} } }提示Refresh组件包裹Scroll组件当用户下拉 Scroll 内容超出顶部时触发刷新。refreshing状态控制刷新指示器的显示onRefresh回调中执行数据加载逻辑。二、Refresh 状态管理2.1 刷新状态枚举// 刷新状态 .onStateChange((refreshStatus: RefreshStatus) { switch (refreshStatus) { case RefreshStatus.INACTIVE: console.log(非刷新状态) break case RefreshStatus.DRAG: console.log(正在下拉) break case RefreshStatus.OVERDRAG: console.log(超过刷新阈值) break case RefreshStatus.REFRESH: console.log(正在刷新) break case RefreshStatus.DONE: console.log(刷新完成) break } })状态说明触发时机INACTIVE非刷新状态默认状态DRAG正在下拉用户手指下拉时OVERDRAG超过阈值下拉距离超过触发值REFRESH正在刷新松手后开始刷新DONE刷新完成刷新结束三、骨架屏设计3.1 骨架屏结构// 骨架屏组件 — 数据加载时的占位显示 Component export struct SkeletonScreen { build() { Column({ space: 16 }) { // 宠物卡片骨架 this.SkeletonCard(80, 100%, 16) // 快捷操作骨架 Row({ space: 8 }) { ForEach([0, 1, 2, 3, 4], (_: number) { this.SkeletonCircle(48) }) } .width(100%) .padding({ left: 16, right: 16 }) // 今日记录骨架 this.SkeletonCard(200, 100%, 12) // 健康提醒骨架 this.SkeletonCard(60, 100%, 12) } .padding({ top: 16 }) } Builder SkeletonCard(height: number, width: string, borderRadius: number) { Column() .width(width) .height(height) .backgroundColor(#F0F0F0) .borderRadius(borderRadius) .margin({ left: 16, right: 16 }) } Builder SkeletonCircle(size: number) { Column() .width(size) .height(size) .backgroundColor(#F0F0F0) .borderRadius(size / 2) } }3.2 骨架屏加载状态管理Entry Component export struct HomePage { State isLoading: boolean true State isRefreshing: boolean false aboutToAppear(): void { this.loadInitialData() } loadInitialData(): void { this.isLoading true // 模拟数据加载 setTimeout(() { this.isLoading false }, 2000) } build() { if (this.isLoading) { // 显示骨架屏 SkeletonScreen() } else { // 显示真实内容 Refresh({ refreshing: this.isRefreshing, onRefresh: this.onRefresh }) { Scroll() { Column({ space: 16 }) { // 首页内容... } } } } } }四、骨架屏动画效果4.1 闪烁动画Component export struct AnimatedSkeleton { State opacity: number 0.3 aboutToAppear(): void { // 骨架屏闪烁动画 animateTo({ duration: 1000, iterations: -1, curve: Curve.EaseInOut }, () { this.opacity 0.7 }) } build() { Column() .width(100%) .height(200) .backgroundColor(#F0F0F0) .borderRadius(16) .opacity(this.opacity) // 透明度变化产生闪烁效果 } }4.2 渐变效果// 使用 LinearGradient 实现渐变骨架屏 Component export struct GradientSkeleton { build() { Column() .width(100%) .height(200) .borderRadius(16) .linearGradient({ direction: GradientDirection.Right, colors: [[#F0F0F0, 0], [#E0E0E0, 0.5], [#F0F0F0, 1]] }) } }五、数据加载状态管理5.1 三种状态// 数据加载的三种状态 enum LoadState { LOADING, // 加载中 SUCCESS, // 加载成功 ERROR // 加载失败 } State loadState: LoadState LoadState.LOADING build() { Column() { if (this.loadState LoadState.LOADING) { this.LoadingView() } else if (this.loadState LoadState.ERROR) { this.ErrorView() } else { this.ContentView() } } }5.2 加载失败视图Builder ErrorView() { Column({ space: 16 }) { Text() .fontSize(64) Text(加载失败) .fontSize(18) .fontWeight(FontWeight.Bold) Text(请检查网络后重试) .fontSize(14) .fontColor(#999999) Button(重新加载) .fontSize(15) .fontColor(Color.White) .backgroundColor(#F5A623) .borderRadius(20) .onClick(() { this.loadState LoadState.LOADING this.loadInitialData() }) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) }六、加载指示器6.1 LoadingProgress 组件// 加载指示器 LoadingProgress() .width(32) .height(32) .color(#F5A623) // 品牌橙色6.2 自定义加载动画// 自定义加载指示器 Builder CustomLoading() { Column({ space: 12 }) { Text() .fontSize(48) .rotate({ angle: 360 }) .animation({ duration: 2000, iterations: -1, curve: Curve.Linear }) Text(加载中...) .fontSize(14) .fontColor(#999999) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) }七、下拉刷新与骨架屏的结合7.1 完整的数据加载流程页面首次加载 ↓ 显示骨架屏 LoadingProgress ↓ 数据加载完成 ↓ 隐藏骨架屏显示真实内容 ↓ 用户下拉刷新 ↓ 显示 Refresh 下拉指示器 ↓ 数据刷新完成 ↓ 隐藏刷新指示器更新内容7.2 代码实现Entry Component export struct HomePage { State isFirstLoad: boolean true State isRefreshing: boolean false State loadState: LoadState LoadState.LOADING aboutToAppear(): void { this.loadData() } loadData(): void { this.loadState LoadState.LOADING // 模拟网络请求 setTimeout(() { this.loadState LoadState.SUCCESS this.isFirstLoad false }, 2000) } build() { Stack() { if (this.isFirstLoad) { // 首次加载显示骨架屏 SkeletonScreen() LoadingProgress() .width(32).height(32) .color(#F5A623) .position({ x: 50%, y: 50% }) } // 真实内容渐变显示 if (this.loadState LoadState.SUCCESS) { Refresh({ refreshing: this.isRefreshing, onRefresh: this.onRefresh }) { Scroll() { Column({ space: 16 }) { // 首页内容... } } } .opacity(this.isFirstLoad ? 0 : 1) .animation({ duration: 300 }) } // 加载失败状态 if (this.loadState LoadState.ERROR) { this.ErrorView() } } } }八、刷新策略设计8.1 自动刷新 vs 手动刷新刷新方式触发条件适用场景手动下拉刷新用户下拉动作用户主动获取最新数据页面加载刷新页面首次加载首次进入页面定时自动刷新定时器触发天气、股票等实时数据事件触发刷新数据变更通知其他页面修改了数据8.2 萌宠日记的刷新策略场景刷新方式刷新内容首页首次加载自动加载所有数据用户下拉刷新手动刷新宠物信息、今日记录、提醒从其他 Tab 返回自动刷新今日记录日记保存后事件触发今日记录摘要九、性能优化9.1 骨架屏性能优化策略说明轻量组件骨架屏使用纯 Column避免复杂组件复用 Builder使用 Builder 复用骨架元素避免动画骨架屏闪烁动画使用 opacity 属性性能开销小及时移除数据加载完成后立即隐藏骨架屏9.2 刷新性能刷新时使用State控制 UI 更新避免直接操作 DOM刷新数据时只更新变化的部分不重新渲染整个页面使用SetTimeout模拟网络延迟实际场景替换为真实网络请求十、最佳实践10.1 加载体验设计原则有序列表 — 加载体验设计的 5 个原则即时反馈用户操作后立即显示加载状态骨架屏或指示器避免白屏数据加载期间始终显示视觉反馈状态完整覆盖加载中、成功、失败三种状态动画适度骨架屏动画不宜过于复杂避免性能开销降级方案加载失败时提供重试按钮避免用户卡在加载页10.2 萌宠日记加载体验总结场景视觉反馈实现方式首次加载骨架屏 加载指示器SkeletonScreen 组件下拉刷新Refresh 下拉指示器Refresh 组件加载失败错误提示 重试按钮ErrorView Builder数据为空空状态提示空状态引导卡片总结本文从萌宠日记的首页加载体验出发深入解析了下拉刷新和骨架屏的完整实现Refresh 组件下拉刷新的容器组件refreshing 和 onRefresh 属性刷新状态管理INACTIVE → DRAG → OVERDRAG → REFRESH → DONE骨架屏设计Builder 复用骨架元素模拟内容布局骨架屏动画闪烁动画和渐变效果加载状态管理LOADING / SUCCESS / ERROR 三种状态加载失败处理错误提示和重试按钮骨架屏与刷新结合首次加载用骨架屏后续刷新用 Refresh刷新策略设计自动刷新、手动刷新、事件触发刷新至此模块三首页看板的 9 篇文章全部完成。下一篇我们将进入模块四宠物档案首篇解析宠物档案页布局与头像设计。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源Refresh 组件参考https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-refreshLoadingProgress 组件https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-loadingprogress动画开发指导https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-animation数据加载与状态管理https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/state-management用户体验设计https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ux-design网络请求指导https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/http-request错误处理最佳实践https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/error-handling应用启动优化https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-startup