HarmonyOS 6.0 骨架屏与加载占位

📅 2026/7/20 16:18:33
HarmonyOS 6.0 骨架屏与加载占位
打开一个应用看到白屏——这是用户最焦虑的时刻。骨架屏就是解决这个问题的在数据加载完之前用灰色占位块模拟页面布局给用户内容马上就来的心理预期。HarmonyOS NEXT没有内置Skeleton组件得自己实现但用Builder封装后复用也不难。为什么需要骨架屏传统的加载方式有三种白屏等待— 最差体验用户不知道是在加载还是崩了Loading转圈— 比白屏好但遮挡了整个页面用户对内容毫无预判骨架屏— 最优体验用户能提前看到页面结构心理预期明确骨架屏的本质是视觉占位——用占位元素告诉用户这里将来会出现什么。所以骨架的布局必须跟真实内容一致不然切换时会有布局跳变。基础骨架组件文本行占位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:booleantrue;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.isLoadingtrue;setTimeout((){this.isLoadingfalse;},3000);})setTimeout模拟异步数据加载3秒后切换到真实内容。列表骨架列表场景需要多个骨架项同时显示StateisLoading:booleantrue;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.isLoadingfalse;})骨架屏设计原则布局一致— 骨架与真实内容的布局结构必须匹配。如果真实内容是Row(头像文字)骨架也必须是Row(圆形线条)。尺寸对应— 骨架元素的宽高应该接近真实内容。头像占位的大小应该跟真实头像一致文字行的高度应该跟真实文字行高接近。颜色柔和— #e0e0e0或#eeeeee不要用深色。骨架是配角不能抢视觉焦点。圆角一致— 文字行用大圆角(borderRadiusheight/2)模拟文字的连续感。图片用跟真实图片一样的borderRadius。宽度变化— 不同行的宽度用不同百分比(40%/60%/80%)模拟自然文本的参差感。不要所有行都是100%宽度。间距对齐— 骨架元素的间距(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模拟加载。简单但有效。