大家好,我是小 z,今天要给大家分享一个提升用户体验的超实用技巧 —— 骨架图🎯
文章目录
- 一、什么是骨架图
- 二、骨架图的作用
- 三、鸿蒙开发中实现骨架图的方法
- 1. 利用 opacity 奠定视觉基础
- 2. animateTo 驱动动态变化
- 3. 二者协同触发与展示
- 四、完整代码
在当今快节奏的数字化时代,用户对于应用程序的体验要求越来越高。一个响应迅速、视觉流畅的应用,往往能在众多竞品中脱颖而出🌟。在鸿蒙开发中,骨架图作为优化用户体验的重要手段,正逐渐受到开发者们的广泛关注。
一、什么是骨架图
骨架图,简单来说,是一种在数据尚未加载完成时,展示页面大致结构的占位图形。它就像一个精心搭建的建筑蓝图🏗️,以简洁的线条和几何形状勾勒出页面的主要元素,如标题栏、列表项、图片区域等。当用户打开应用,首先映入眼帘的不再是一片空白,而是一个大致的页面框架,为后续填充具体内容提供了基础。
二、骨架图的作用
- 减少用户等待焦虑😟:在网络环境不佳或数据量较大的情况下,数据加载可能需要一定时间。若没有骨架图,用户面对的将是长时间的空白屏幕,这极易引发用户的焦虑情绪,甚至导致用户放弃使用应用。而骨架图的出现,让用户明确知道页面正在加载,并且对即将呈现的内容有了初步预期,从而有效缓解等待过程中的焦虑。
- 提升视觉连贯性🎨:从空白页面到突然加载出完整内容,这种突兀的转变可能会给用户带来视觉上的不适感。骨架图则能在加载过程中保持页面的视觉连贯性,通过逐渐过渡到真实内容,为用户提供一种流畅、自然的视觉体验。
三、鸿蒙开发中实现骨架图的方法
在鸿蒙开发中,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的变化范围,从而打造出各种独具特色且舒适的骨架图加载效果。让我们一起用这些技术,为用户带来更加精彩的应用体验吧🎉!