鸿蒙开发中的骨架图:提升用户体验的关键一环
大家好,我是小 z,今天要给大家分享一个提升用户体验的超实用技巧 —— 骨架图🎯
在当今快节奏的数字化时代,用户对于应用程序的体验要求越来越高。一个响应迅速、视觉流畅的应用,往往能在众多竞品中脱颖而出🌟。在鸿蒙开发中,骨架图作为优化用户体验的重要手段,正逐渐受到开发者们的广泛关注。
一、什么是骨架图
骨架图,简单来说,是一种在数据尚未加载完成时,展示页面大致结构的占位图形。它就像一个精心搭建的建筑蓝图🏗️,以简洁的线条和几何形状勾勒出页面的主要元素,如标题栏、列表项、图片区域等。当用户打开应用,首先映入眼帘的不再是一片空白,而是一个大致的页面框架,为后续填充具体内容提供了基础。
二、骨架图的作用
- 减少用户等待焦虑😟:在网络环境不佳或数据量较大的情况下,数据加载可能需要一定时间。若没有骨架图,用户面对的将是长时间的空白屏幕,这极易引发用户的焦虑情绪,甚至导致用户放弃使用应用。而骨架图的出现,让用户明确知道页面正在加载,并且对即将呈现的内容有了初步预期,从而有效缓解等待过程中的焦虑。
- 提升视觉连贯性🎨:从空白页面到突然加载出完整内容,这种突兀的转变可能会给用户带来视觉上的不适感。骨架图则能在加载过程中保持页面的视觉连贯性,通过逐渐过渡到真实内容,为用户提供一种流畅、自然的视觉体验。
三、鸿蒙开发中实现骨架图的方法
在鸿蒙开发中,opacity(透明度)属性与animateTo函数的精妙搭配,为构建引人入胜的骨架图效果提供了有力支持,极大地优化了用户体验。
1. 利用 opacity 奠定视觉基础
在给定的代码里,TravelSkeletonView组件通过@State columnOpacity: number = 1定义了透明度状态变量columnOpacity ,初始值设为 1。这意味着在页面加载的初始阶段,骨架图以完全不透明的状态呈现,用户能够清晰看到页面的大致结构,比如各个占位元素所代表的区域布局。这种清晰的初始展示,为后续的动态变化提供了稳定的视觉基准,就像是搭建舞台,先把布景布置好🎭。
2. animateTo 驱动动态变化
startAnimation(): void{
animateTo(CommonConstants.SKELETON_ANIMATION, () => {
this.columnOpacity = 0.5
})
}
- animateTo函数在整个骨架图动态展示中扮演着核心驱动角色。它接收两个关键参数,第一个参数CommonConstants.SKELETON_ANIMATION 详细定义了动画的各项特性。其中,持续时间设定为 400 毫秒,这决定了整个动画从开始到结束的时长;节奏设为 0.6,影响动画的速度变化;缓动曲线选择Curve.EaseInOut,使得动画在开始和结束时都较为平滑,避免突兀;延迟时间 200 毫秒,让动画在组件出现 200 毫秒后才启动,给予用户一定的适应时间;迭代次数设为无限次,且播放模式为PlayMode.Alternate(交替播放),这意味着动画会不断循环,且每次播放方向相反。想象一下,这就像是舞台上的灯光在慢慢闪烁,吸引着观众的注意力✨。
- 第二个参数是一个回调函数,当动画执行时,会将columnOpacity的值从初始的 1 改变为 0.5。结合opacity属性来看,在build方法中,Column组件通过.opacity(this.columnOpacity)将自身的透明度与columnOpacity绑定。所以,随着animateTo函数驱动columnOpacity值的改变,整个骨架图组件的透明度会逐渐降低。这一过程模拟出数据加载时,骨架图逐渐被真实内容替代的视觉效果,给用户一种数据正在逐步填充的直观感受,仿佛舞台上的演员在慢慢走上台,替换掉了之前的占位道具🎭。
3. 二者协同触发与展示
在build方法里,.opacity(this.columnOpacity).onAppear(() => { this.startAnimation() })这部分代码至关重要。.opacity(this.columnOpacity)确保了骨架图组件的透明度实时跟随columnOpacity值变化。而.onAppear(() => { this.startAnimation() })则表明,当骨架图组件被渲染到屏幕上时,startAnimation方法会立即被触发。也就是说,组件一出现,animateTo函数驱动的动画就开始改变骨架图的透明度,从而向用户展示数据加载的动态过程。这种动态变化不仅增强了页面的视觉层次感,还巧妙地暗示了用户数据加载的进程,让用户在等待数据的过程中,有更丰富的视觉反馈,而不是面对单调的空白等待。
四、完整代码
- 使用的color和AnimateParam对象
{
"color": [
{
"name": "color_1",
"value": "#ff0000"
},
{
"name": "skeleton_color_deep",
"value": "#1A000000"
},
{
"name": "skeleton_color_medium",
"value": "#FFF2F3F4"
},
{
"name": "skeleton_color_light",
"value": "#FFECECEC"
},
{
"name": "skeleton_color",
"value": "#ECECEC"
}
]
}
static readonly SKELETON_ANIMATION: AnimateParam = {
duration: 400,
tempo: 0.6,
curve: Curve.EaseInOut,
delay: 200,
iterations: -1,
playMode: PlayMode.Alternate
}
- 整体骨架视图
import { BreakpointConstants, BreakpointType, CommonConstants } from "utils";
import { NearbySpotLoadingSkeleton } from "./NearbySpotLoadingSkeleton";
const NEARBY_VISIBLE_LENGTH = 10
@Component
export struct TravelSkeletonView {
nearbySpots: Array<Number> = new Array(NEARBY_VISIBLE_LENGTH).fill(1).map((v: number, index: number) => index + 1);
@State columnOpacity: number = 1
@StorageLink('currentHeightBreakpoint') currentHeightBreakpoint: string = BreakpointConstants.BREAKPOINT_LG;
@StorageLink('currentWidthBreakpoint') currentWidthBreakpoint: string = BreakpointConstants.BREAKPOINT_LG;
startAnimation(): void{
animateTo(CommonConstants.SKELETON_ANIMATION, () => {
this.columnOpacity = 0.5
})
}
build() {
Column() {
Row() {
Row() {
}
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Center)
.backgroundColor($r('app.color.skeleton_color'))
.height(20)
.width('20%')
.padding(5)
Blank()
Row() {
}
.width('15%')
.height(20)
.backgroundColor($r('app.color.skeleton_color'))
}
.alignItems(VerticalAlign.Center)
.width('100%')
List() {
ForEach(this.nearbySpots, (item:number) => {
ListItem() {
NearbySpotLoadingSkeleton()
}
.margin({ left: 5, right: 5 })
})
}
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
.lanes(new BreakpointType({ sm: 1, md: 1, lg: 2 }).getValue(this.currentWidthBreakpoint))
.scrollBar(BarState.Off)
.width('100%')
.layoutWeight(1)
.listDirection(Axis.Vertical)
}
.opacity(this.columnOpacity)
.onAppear(() => {
this.startAnimation()
})
.alignItems(HorizontalAlign.Start)
.width('100%')
}
}
- 具体渲染骨架组件
import Constants from "../constants/Constants"
@Component
export struct NearbySpotLoadingSkeleton{
build() {
Row() {
Column() {
Row()
.width('100%')
.height(60)
.backgroundColor($r('app.color.skeleton_color_deep'))
Row()
.height(20)
.width("40%")
.margin({ top: 5})
.backgroundColor($r('app.color.skeleton_color_medium'))
}
.width('40%')
.padding(8)
.alignItems(HorizontalAlign.Center)
Column() {
Row()
.height(20)
.width("30%")
.backgroundColor($r('app.color.skeleton_color_medium'))
Row()
.height(20)
.width('80%')
.padding({ top: 5 })
.backgroundColor($r('app.color.skeleton_color_light'))
}
.layoutWeight(1)
.alignItems(HorizontalAlign.End)
}
.borderRadius(Constants.BORDER_RADIUS_MD)
.backgroundColor(Color.White)
.height(100)
.width('100%')
.padding(5)
.margin({ top: 5, bottom: 5, left: 10, right: 10})
}
}
在实际开发中,nearbySpots: Array = new Array(NEARBY_VISIBLE_LENGTH).fill(1).map((v: number, index: number) => index + 1);确定了要渲染的骨架数量。开发者完全可以依据应用的整体风格以及用户体验需求,灵活调整animateTo函数中的动画参数,如时长、缓动曲线、关键帧等,同时搭配opacity的变化范围,从而打造出各种独具特色且舒适的骨架图加载效果。让我们一起用这些技术,为用户带来更加精彩的应用体验吧🎉!
原文地址:https://blog.csdn.net/m0_66825548/article/details/145268232
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!