HarmonyOS 6.0 骨架屏与加载占位
2026/7/20 16:18:28 网站建设 项目流程

打开一个应用看到白屏——这是用户最焦虑的时刻。骨架屏就是解决这个问题的:在数据加载完之前,用灰色占位块模拟页面布局,给用户"内容马上就来"的心理预期。HarmonyOS NEXT没有内置Skeleton组件,得自己实现,但用@Builder封装后复用也不难。

为什么需要骨架屏

传统的加载方式有三种:

  1. 白屏等待— 最差体验,用户不知道是在加载还是崩了
  2. Loading转圈— 比白屏好,但遮挡了整个页面,用户对内容毫无预判
  3. 骨架屏— 最优体验,用户能提前看到页面结构,心理预期明确

骨架屏的本质是"视觉占位"——用占位元素告诉用户这里将来会出现什么。所以骨架的布局必须跟真实内容一致,不然切换时会有布局跳变。

基础骨架组件

文本行占位

@BuilderSkeletonLine(width:Length):void{Row().width(width).height(14).borderRadius(7).backgroundColor('#e0e0e0')}

关键设计:

  • 高度14vp,接近正文行高
  • borderRadius设高度的一半(7vp),模拟文字的圆角感
  • 颜色用#e0e0e0浅灰,不抢视觉焦点
  • width用百分比(‘60%’/‘80%’),不同行不同宽度模拟自然文本

头像占位

@BuilderSkeletonCircle(size:number):void{Row().width(size).height(size).borderRadius(size/2).backgroundColor('#e0e0e0')}

头像占位用圆形,size参数控制大小。48vp是常见头像尺寸,36vp是列表头像。

卡片骨架

@BuilderSkeletonCard():void{Row(){this.SkeletonCircle(48)// 头像Column(){this.SkeletonLine('60%')// 标题this.SkeletonLine('80%')// 副标题}.layoutWeight(1).margin({left:12}).alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.SpaceAround)}.width('100%').padding(16).borderRadius(12).backgroundColor('#ffffff')}

头像+文字行是卡片骨架最经典的模式——Circle在左,纵向Line在右。

列表项骨架

@BuilderSkeletonListItem():void{Row(){this.SkeletonCircle(36)Column(){this.SkeletonLine('50%')this.SkeletonLine('70%')this.SkeletonLine('40%')}.layoutWeight(1).margin({left:10}).alignItems(HorizontalAlign.Start)}.width('100%').padding(10).borderRadius(8).backgroundColor('#ffffff').margin({bottom:6})}

列表骨架重复相同模式,宽度用不同百分比区分每行。

条件渲染切换

骨架屏最核心的交互就是加载/完成的切换:

@StateisLoading:boolean=true;if(this.isLoading){this.SkeletonCard()}else{Column(){Text('数据加载成功!').fontSize(16).fontWeight(FontWeight.Medium).fontColor('#43a047')// ...真实内容}.padding(16).borderRadius(12).backgroundColor('#ffffff').width('100%')}

切换按钮:

Button(this.isLoading?'显示内容':'显示骨架').onClick(()=>{this.isLoading=!this.isLoading;})

模拟网络加载:

Button('加载3秒').onClick(()=>{this.isLoading=true;setTimeout(()=>{this.isLoading=false;},3000);})

setTimeout模拟异步数据加载,3秒后切换到真实内容。

列表骨架

列表场景需要多个骨架项同时显示:

@StateisLoading:boolean=true;List(){if(this.isLoading){ForEach([0,1,2,3,4],(item:number)=>{ListItem(){this.SkeletonListItem()}},(item:number)=>item.toString())}else{ForEach(realData,(msg:string,index:number)=>{ListItem(){// 真实列表项}},(msg:string,index:number)=>index.toString())}}

5个骨架项是列表骨架的标配——太少了看不出效果,太多了浪费渲染。5-8个是比较合理的范围。

过渡动画

骨架到内容的切换如果硬切,会有跳变感。加个opacity过渡:

Column(){// 内容}.transition(TransitionEffect.OPACITY)

transition让组件出现/消失时有透明度过渡。但这只在组件级别生效,骨架和内容是条件渲染互斥的,需要在两方都加transition才能实现平滑切换。

更高级的做法是用animateTo控制切换:

animateTo({duration:300,curve:Curve.EaseInOut},()=>{this.isLoading=false;})

骨架屏设计原则

  1. 布局一致— 骨架与真实内容的布局结构必须匹配。如果真实内容是Row(头像+文字),骨架也必须是Row(圆形+线条)。

  2. 尺寸对应— 骨架元素的宽高应该接近真实内容。头像占位的大小应该跟真实头像一致,文字行的高度应该跟真实文字行高接近。

  3. 颜色柔和— #e0e0e0或#eeeeee,不要用深色。骨架是配角,不能抢视觉焦点。

  4. 圆角一致— 文字行用大圆角(borderRadius=height/2)模拟文字的连续感。图片用跟真实图片一样的borderRadius。

  5. 宽度变化— 不同行的宽度用不同百分比(40%/60%/80%),模拟自然文本的参差感。不要所有行都是100%宽度。

  6. 间距对齐— 骨架元素的间距(margin/padding)应该跟真实内容的间距一致。

文章骨架模式

长文章的骨架比较复杂——有大标题、多段正文、可能还有图片占位:

Column(){Row(){this.SkeletonCircle(56)// 作者头像(大)Column(){this.SkeletonLine('40%')// 作者名this.SkeletonLine('100%')// 标题(长)this.SkeletonLine('90%')// 摘要1this.SkeletonLine('60%')// 摘要2}.layoutWeight(1).margin({left:12}).alignItems(HorizontalAlign.Start)}.width('100%')}.padding(16).borderRadius(12).backgroundColor('#ffffff')

文章骨架的特点是文字行多、宽度差异大,模拟真实文章的长短句。

性能注意

  • 骨架屏渲染量很小(几个灰色矩形),性能不是问题
  • 切换时如果列表数据量大,ForEach初始渲染可能有延迟——骨架屏正好填充这段时间
  • 不要在骨架上做复杂动画(闪烁、波纹),简单的占位就够了
  • 条件渲染(if/else)比opacity显隐更适合骨架场景——骨架不需要占空间但不可见

踩坑清单

问题原因解决
骨架切换跳变布局不一致骨架布局匹配真实内容
骨架行全是100%宽没做宽度变化用40%-80%不等的百分比
骨架太刺眼颜色太深用#e0e0e0浅灰
切换无过渡没加transition加TransitionEffect.OPACITY
骨架圆角太锐borderRadius太小设为height/2
列表骨架太少只放了1-2个放5-8个骨架项

骨架屏不是技术难题——它更像是设计意识的体现。同样的加载等待,骨架屏让用户觉得"快"了,因为心理预期被提前满足了。实现上就三个步骤:@Builder封装占位组件、if/else条件渲染切换、setTimeout模拟加载。简单但有效。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询