HarmonyOS开发案例:优雅的实现一个骨架屏
HarmonyOS优雅的实现一个骨架屏(包含v2装饰器,动画,业务逻辑)
·
题外话:什么是OOP
直言:面向对象是一种思想。
封装是为了将接口和实现分离,其实就是为了解耦。
继承就是代码的复用 ,子类继承父类的方法。
多态呢,多态稍微复杂一点,多态的话,可能就是说使用至不同的内部结构的对象,
可以共享相同的外部接口,其实在我们的理解就是这个, 相同的方法名,相同的返回值,
返回值定义,然后他的参数不一样。
其实对外暴露的时候,方法名都是一个,参数不一样,不同场景用的同一个方法。
正题:骨架屏实现
先看效果:
仿开眼logo 侵权可删

定义enum页面加载状态
/**
* 页面加载状态 Loading
*/
export enum LoadingStatus {
OFF = 'off', //关
LOADING = 'loading', //加载中
SUCCESS = 'success', //加载成功
FAILED = 'failed' //加载失败
}
设置一点全局方法+动画实现
//常用参数
export class CommonConstants {
static readonly FULL_SCREEN = '100%'
// 骨架动画
static readonly SKELETON_ANIMATION: AnimateParam = {
duration: 400, //动画持续时间,单位为毫秒
tempo: 0.6, //动画播放速度。数值越大动画播放速度越快,数值越小动画播放速度越慢。值为0表示没有动画
curve: Curve.EaseInOut, //动画曲线:表示动画以慢速开始和结束
delay: 200, //动画延迟时间,单位为毫秒
iterations: -1, //动画的迭代。当设置为-1时,动画重复播放。取值范围大于等于-1
//playMode: 动画播放模式。默认情况下,动画在播放完成后从头开始播放
playMode: PlayMode.Alternate //PlayMode.Alternate: 动画在奇数(1,3,7…)上向前播放,在偶数(2,4,6…)上向后播放
}
}
//全局方法(demo偷懒用全局方法, 实际开发中,应该用资源文件存储 )
export function skeletonColor2(): string {
return '#FFF2F3F4'. //骨架屏色(狠浅)
}
export function skeletonColor(): string {
return '#ECECEC'. //骨架屏色(偏浅)
}
export function skeletonColorDeep(): string {
return '1A000000' //骨架屏色 (偏深)
}
export function vpConversion(number: number): string {
return number + 'vp' //无须多言 vp
}
骨架屏View
@Preview //方便在预览器看效果 开发完后删除该装饰器
@ComponentV2
export struct HomeSkeletonView {
//不透明度
@Local columnOpacity: number = 1;
//开始动画
startAnimation(): void {
animateTo(CommonConstants.SKELETON_ANIMATION, () => {
this.columnOpacity = 0.5;
})
}
build() {
Column() {
//tab
Row({ space: vpConversion(8) }) {
ForEach([7, 7, 7, 7, 7, 7, 7], () => {
Row()
.width(vpConversion(44))
.height(vpConversion(24))
.backgroundColor(skeletonColor())
.padding({
left: vpConversion(12)
})
.borderRadius(vpConversion(5))
})
}
.margin({ top: vpConversion(10) })
.width(CommonConstants.FULL_SCREEN)
ForEach([3, 3, 3], () => {
Column({ space: vpConversion(8) }) {
Row()
.backgroundColor(skeletonColorDeep())
.width(CommonConstants.FULL_SCREEN)
.height(vpConversion(150))
.borderRadius(vpConversion(10))
.margin({ top: vpConversion(10), bottom: vpConversion(6) })
Row()
.backgroundColor(skeletonColor())
.width('84%')
.height(vpConversion(12))
.borderRadius(vpConversion(10))
Row()
.backgroundColor(skeletonColor())
.width('84%')
.height(vpConversion(12))
.borderRadius(vpConversion(10))
Row({ space: vpConversion(12) }) {
Row()
.backgroundColor(skeletonColorDeep())
.width(vpConversion(30))
.height(vpConversion(30))
.borderRadius(vpConversion(50))
Column() {
Row()
.backgroundColor(skeletonColor())
.width('35%')
.height(vpConversion(10))
.borderRadius(vpConversion(10))
Row()
.backgroundColor(skeletonColor())
.width('26%')
.height(vpConversion(10))
.borderRadius(vpConversion(10))
}
.height(vpConversion(30))
.width(CommonConstants.FULL_SCREEN)
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.SpaceAround)
}
Line()
.height(vpConversion(1))
.width('97%')
.margin({ left: vpConversion(3) })
.backgroundColor('#E0E0E0')
}
.width(CommonConstants.FULL_SCREEN)
.alignItems(HorizontalAlign.Start)
})
}
.backgroundColor($r('app.color.skeletonColor2'))
.padding({ left: 12, right: 12 })
.width(CommonConstants.FULL_SCREEN)
.height(CommonConstants.FULL_SCREEN)
.opacity(this.columnOpacity) //动态控制不透明度
.onAppear(() => {
this.startAnimation() //当ui挂载显示器时触发的回调-> 执行(开始动画)方法
})
}
}
目前我们的骨架屏已经实现了,但是还是需要完善一下逻辑。
骨架屏使用
import { LoadingStatus } from "common";
import { HomeSkeletonView } from "../../../components/HomeSkeletonView"
import { VideoTypeEntity } from "../../../model/VideoTypeEntity";
import { videoTypeService } from "../../../service/VideoTypeService";
import { RecommendTitleBar } from "./recommend/RecommendTitleBar";HttpCodezH
@ComponentV2
export struct RecommendView {
//骨架屏 状态
@Local loadingStatus: LoadingStatus = LoadingStatus.OFF //默认关闭
//data
@Local videoTypes: Array<VideoTypeEntity> = []
aboutToAppear(): void {
this.loadData()
}
loadData(): void {
// 进入页面,状态设置为loading,也就是骨架屏
this.loadingStatus = LoadingStatus.LOADING
// 模拟一个网络请求,自行替换
videoTypeService.getVideoTypes().then((result) => {
//成功
if (result.code == 200) {
this.videoTypes = result.data //替换data数据
//修改页面状态为success
this.loadingStatus = LoadingStatus.SUCCESS
} else {
// 修改页面状态为falled,没有获取到数据
this.loadingStatus = LoadingStatus.FAILED
}
})
}
build() {
Column() {
if (this.loadingStatus === LoadingStatus.LOADING) {
HomeSkeletonView() //展示骨架屏view
} else if (this.loadingStatus === LoadingStatus.SUCCESS) {
RecommendTitleBar() //展示数据view
} else if (this.loadingStatus === LoadingStatus.FAILED) {
//加载 无网络视图 TODO
FalledView() //展示失败view
}
}
}
}
原理刨析
业务逻辑:
通过页面状态loading,在数据回来之前,把骨架屏展示出来
核心技术点:
动画实现:SKELETON_ANIMATION(请看代码注解)
通过透明度 0.5 - 1 之间的不断变换,实现了骨架屏若隐若现
通过@Local装饰器实时更新透明度
通过根布局属性onAppear 页面挂载时触发动画
更多推荐



所有评论(0)