HarmonyOS7 页面骨架如何做成组件:ArkUI/ArkTS 实战拆解
文章目录
前言
骨架屏不是灰色占位块越多越专业。它应该贴近真实页面结构,让用户知道内容正在加载,也让页面从加载到完成时不会明显跳动。
这篇单独聊 通用加载 这个场景。重点不是堆 API,而是把骨架屏抽成足够小的 ArkUI 构建器,让加载态贴近真实页面结构。
为什么这个问题经常被写乱
页面骨架如何做成组件 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。
所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。
场景:资讯列表首屏加载
骨架屏不是把页面涂成一片灰色。它应该告诉用户:这里会有标题、这里会有摘要、这里会有列表卡片。资讯、课程、订单、商品列表这类页面,如果加载时只放一个转圈,用户不知道还要等多久;如果骨架结构贴近真实内容,等待感会明显降低。
我做骨架组件时会控制复杂度。骨架应该是轻量的 UI 片段,不应该复制一份完整业务卡片。复制得越像,维护成本越高;抽得太粗,又失去降低跳动的意义。
骨架屏不是装饰,它是“页面马上会长什么样”的预告。
骨架块设计
| 骨架块 | 对应真实内容 | 尺寸建议 | 注意点 |
|---|---|---|---|
SkeletonLine |
标题、摘要 | 12 到 22 vp 高 | 宽度不要全满 |
SkeletonAvatar |
头像、图标 | 32 到 48 vp | 圆角贴近真实图形 |
SkeletonCard |
列表卡片 | 接近真实卡片高度 | 避免加载完成大跳动 |
SkeletonAction |
按钮 | 32 到 40 vp 高 | 不要太抢眼 |
实操步骤
- 先画真实页面结构,再反推骨架块,而不是先画灰块。
- 抽出最小单元
SkeletonLine,复用颜色和圆角。 - 再组合出
ArticleSkeletonCard这类业务骨架。 - 加载态和内容态用同一块页面区域切换,避免高度差过大。
- 骨架数量与首屏可见内容接近,不要一次渲染几十个灰块。
- 接口失败时切到错误态,不要让骨架无限显示。
先把页面目标想清楚
在真正写代码之前,先别急着盯着 API。更有用的做法是先想清楚:这个页面到底想解决什么问题,用户最在意的反馈是什么,哪些状态必须一直保持一致。
当你先把这条主线想明白,再回头看组件和状态设计,很多选择都会顺理成章。对小白来说,这一步尤其重要,因为它能帮你从“照着抄”慢慢过渡到“看得懂、改得动”。
完整示例:资讯卡片骨架组件
interface ArticleItem {
id: number
title: string
summary: string
author: string
}
@Entry
@Component
struct SkeletonComponentPage {
@State loading: boolean = true
@State failed: boolean = false
private articles: ArticleItem[] = [
{ id: 1, title: 'ArkUI 列表加载的几个细节', summary: '从骨架屏、空态到错误兜底,整理一个更完整的加载体验。', author: '编辑部' },
{ id: 2, title: 'HarmonyOS7 页面状态管理实践', summary: '用明确状态减少页面闪动和重复判断。', author: '技术团队' }
]
@Builder
SkeletonLine(widthValue: Length, heightValue: number, radiusValue: number) {
Blank()
.width(widthValue)
.height(heightValue)
.backgroundColor('#E6EAF0')
.borderRadius(radiusValue)
}
@Builder
ArticleSkeletonCard() {
Row({ space: 12 }) {
this.SkeletonLine(48, 48, 8)
Column({ space: 10 }) {
this.SkeletonLine('64%', 18, 4)
this.SkeletonLine('92%', 14, 4)
this.SkeletonLine('48%', 14, 4)
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.padding(14)
.backgroundColor('#FFFFFF')
.borderRadius(8)
}
@Builder
ArticleCard(item: ArticleItem) {
Row({ space: 12 }) {
Text(item.author.substring(0, 1))
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.width(48)
.height(48)
.textAlign(TextAlign.Center)
.backgroundColor('#0A59F7')
.borderRadius(8)
Column({ space: 6 }) {
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(item.summary)
.fontSize(13)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(item.author)
.fontSize(12)
.fontColor('#999999')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.padding(14)
.backgroundColor('#FFFFFF')
.borderRadius(8)
}
private reload() {
this.loading = true
this.failed = false
}
build() {
Column({ space: 12 }) {
Row() {
Text('加载示例')
.fontSize(22)
.fontWeight(FontWeight.Bold)
Blank()
Button(this.loading ? '显示内容' : '显示骨架')
.onClick(() => this.loading = !this.loading)
}.width('100%')
if (this.failed) {
Column({ space: 8 }) {
Text('加载失败').fontSize(18).fontWeight(FontWeight.Medium)
Text('网络不稳定,请稍后重试。').fontSize(13).fontColor('#666666')
Button('重试').onClick(() => this.reload())
}.alignItems(HorizontalAlign.Start).padding(14).backgroundColor('#FFFFFF').borderRadius(8)
} else if (this.loading) {
this.ArticleSkeletonCard()
this.ArticleSkeletonCard()
this.ArticleSkeletonCard()
} else {
ForEach(this.articles, (item: ArticleItem) => {
this.ArticleCard(item)
}, (item: ArticleItem) => item.id.toString())
}
}
.padding(16)
.height('100%')
.backgroundColor('#F5F7FA')
}
}
把关键代码一段段拆开
SkeletonLine() 是最小单元,只关心宽、高、圆角。这样标题线、摘要线、头像占位都能复用同一套灰色样式,不需要到处复制 backgroundColor。
ArticleSkeletonCard() 的结构和真实 ArticleCard() 接近:左侧头像,右侧三行文字。加载完成后高度变化很小,页面不会突然跳动。
failed 和 loading 分开处理。骨架只能表示“正在加载”,不能拿来表示“加载失败”。接口失败后如果骨架一直转,用户会以为还在等。
容易踩坑的点
- 骨架和真实内容高度差太大,加载完成时页面明显跳动。
- 骨架数量过多,反而增加首屏渲染压力。
- 把骨架写成完整业务组件的复制版,后续维护两套结构。
- 失败后仍显示骨架,没有错误兜底。
- 所有灰块宽度都一样,看起来不像真实内容。
优化建议
骨架屏适合超过短暂等待的页面。如果接口通常几十毫秒返回,强行闪一下骨架反而打扰用户。可以设置一个很小的延迟策略:请求很快完成就不展示骨架,请求超过一定时间再展示。
颜色上不要用太重的灰,尤其是在浅色背景里。骨架应该低调地暗示结构,不应该抢走真实内容的位置感。深色模式下也要单独调整骨架色,否则灰块可能过亮。
骨架组件要轻,但不能失真
骨架屏的价值在于降低等待感和布局跳动,不是复制一套完整业务 UI。灰块要贴近真实内容结构,让用户知道页面马上会出现什么;但它又不能和真实卡片完全同步维护,否则后面改一次业务卡片就要改两份代码。
示例先抽 SkeletonLine() 作为最小单元,再组合成 ArticleSkeletonCard()。这样颜色、圆角和尺寸可以复用,资讯卡片的左图右文结构也能保留下来,加载完成时页面不会明显跳动。
写在最后
失败态要和加载态分开。骨架只表示“正在加载”,接口失败后应该切到错误提示和重试入口。如果骨架无限显示,用户会以为还在等待,实际已经没有下一步了。
更多推荐

所有评论(0)