鸿蒙应用开发实战【99】— 用户体验优化加载动画与触觉反馈
·
鸿蒙应用开发实战【99】— 用户体验优化加载动画与触觉反馈
本文是「号码助手全栈开发系列」第 99 篇,持续更新中…
开源社区:https://openharmonycrossplatform.csdn.net
前言
优秀的用户体验不仅仅是功能完善——加载状态、转场动画和触觉反馈同样重要。号码助手项目在多个场景中应用了动画和 Loading 状态,本篇总结 ArkUI 中的动画最佳实践。
本篇涵盖:AppAnimations 动画令牌、
animateTo隐式动画、页面转场动画、LoadingProgress 加载状态、@ohos.vibrator 触觉反馈、Skeleton 骨架屏。

一、动画令牌系统
文件:common/theme/AppAnimations.ets
import curves from '@ohos.curves'
export class AppAnimations {
// 三档时长
static readonly DURATION_FAST: number = 160 // 快速(hover/按下)
static readonly DURATION_NORMAL: number = 260 // 常规(转场/展开)
static readonly DURATION_SLOW: number = 380 // 慢速(强调动画)
// 两条曲线
static readonly CURVE_STANDARD: ICurve =
curves.cubicBezierCurve(0.2, 0, 0, 1) // 平滑缓入缓出
static readonly CURVE_SPRING: ICurve =
curves.springMotion(0.35, 0.9) // 弹簧效果
// 快捷方法
static standard(duration: number = AppAnimations.DURATION_NORMAL): AnimateParam {
return {
duration,
curve: AppAnimations.CURVE_STANDARD
}
}
}
二、animateTo 隐式动画
ArkUI 最常用的动画方式是通过 animateTo 包裹状态变化:
// FAB 展开/收起动画
private toggleFab(): void {
animateTo(AppAnimations.standard(AppAnimations.DURATION_NORMAL), () => {
this.fabExpanded = !this.fabExpanded
})
}
// 列表项出现动画
ForEach(this.rows, (item: AppRow, index: number) => {
AppListItem({ item, selected: this.isSelected(item.binding.id!) })
.opacity(this.rows.indexOf(item) < this.visibleCount ? 1 : 0)
.translate({
y: this.rows.indexOf(item) < this.visibleCount ? 0 : 20
})
}, (item: AppRow) => item.binding.id!.toString())
动画参数对照:
| 参数 | AppAnimations 值 | 效果 |
|---|---|---|
| duration 160ms | FASTE’ | 反馈型(按钮按下) |
| duration 260ms | NORMAL | 转场型(页面切换) |
| duration 380ms | SLOW | 展示型(强调动画) |
| cubicBezier(0.2,0,0,1) | CURVE_STANDARD | 自然平滑 |
| springMotion(0.35,0.9) | CURVE_SPRING | 弹性回弹 |
三、页面转场动画
通过 @EnterTransition 和 @ExitTransition 定义页面切换动画:
@Entry
@Component
struct BackupPage {
// 进入:从右侧滑入 + 透明度
@EnterTransition
enterTransition: TransitionEffect =
TransitionEffect.slide(SlideEffect.Right)
.combine(TransitionEffect.opacity(1))
// 退出:从下侧滑出
@ExitTransition
exitTransition: TransitionEffect =
TransitionEffect.slide(SlideEffect.Bottom)
}
或者使用 pageTransition API:
// 页面级转场
pageTransition() {
PageTransitionEnter({ duration: 260, curve: AppAnimations.CURVE_STANDARD })
.slide(SlideEffect.Right)
.translate({ x: 100 })
PageTransitionLeave({ duration: 160 })
.slide(SlideEffect.Left)
}
四、加载状态(LoadingProgress)
4.1 全屏 Loading
// HomePage 加载中展示
if (this.loading) {
Column() {
LoadingProgress()
.width(48)
.height(48)
.color(AppColors.PRIMARY)
Text('加载中...')
.fontSize(14)
.fontColor(AppColors.TEXT_2)
.margin({ top: 12 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
4.2 按钮内联 Loading
// 备份按钮 Loading
Row() {
if (this.backing) {
LoadingProgress()
.width(20)
.height(20)
.color('#FFFFFF')
} else {
Text('立即备份')
}
}
.width('100%')
.height(46)
.onClick(() => { this.doBackup() })
4.3 三态 UI 管理
@State loading: boolean = true
@State loadError: string | null = null
build() {
if (this.loading) {
// Loading 态
this.LoadingView()
} else if (this.loadError) {
// 错误态
this.ErrorView(this.loadError)
} else {
// 正常内容
this.ContentView()
}
}
五、触觉反馈
HarmonyOS 提供 @ohos.vibrator 模块实现触觉反馈:
import { vibrator } from '@kit.SensorServiceKit'
// 短促点击反馈
function hapticTap(): void {
try {
vibrator.startVibration({
type: 'time',
duration: 10 // 10ms
}, {
usage: 'touch'
})
} catch (e) {
// 设备不支持震动时静默忽略
}
}
// 长按反馈
function hapticLongPress(): void {
try {
vibrator.startVibration({
type: 'time',
duration: 30 // 30ms
}, {
usage: 'touch'
})
} catch (e) {
// 静默忽略
}
}
使用场景:
// FAB 按钮点击
Image($r('app.media.ic_add'))
.onClick(() => {
hapticTap() // 触觉反馈
this.toggleFab()
})
// 长按列表项进入选择模式
Column()
.gesture(
LongPressGesture({ duration: 500 })
.onAction(() => {
hapticLongPress() // 长按震动
this.enterSelectMode()
})
)
六、Skeleton 骨架屏
数据加载前展示骨架屏,提升感知性能:
@Builder
private SkeletonView() {
Column() {
// 统计卡片骨架
Row() {
ForEach([1, 2, 3], () => {
Column() {
Row()
.width(60)
.height(16)
.borderRadius(8)
.backgroundColor('#1A141E2E')
Row()
.width(40)
.height(32)
.borderRadius(8)
.backgroundColor('#1A141E2E')
.margin({ top: 8 })
}
.padding(16)
.backgroundColor('#0F141E2E')
.borderRadius(16)
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
// 列表骨架
ForEach([1, 2, 3, 4], () => {
Row() {
Circle()
.width(44)
.height(44)
.fill('#1A141E2E')
Column() {
Row()
.width('60%')
.height(14)
.borderRadius(7)
.backgroundColor('#1A141E2E')
Row()
.width('40%')
.height(12)
.borderRadius(6)
.backgroundColor('#1A141E2E')
.margin({ top: 6 })
}
.margin({ left: 12 })
}
.width('100%')
.height(64)
.padding({ left: 16, right: 16 })
})
}
.padding(16)
}
七、动画性能注意事项
| 注意项 | 原因 | 建议 | 难度 |
|---|---|---|---|
| 避免重排 | layout 属性变化触发重新测量 | 用 opacity / translate 代替改宽高 |
低 |
| 减少逐帧计算 | 复杂动画卡顿 | 使用 springMotion 代替逐帧 animate |
中 |
| 动画复用 | 每个组件的 animateTo 独立 | 提取到动画令牌统一管理 | 中 |
| 在页面销毁时停止 | 组件销毁后动画回调报错 | 在 aboutToDisappear 中清理定时器 |
高 |
小结
| 技术 | 用途 | 实现方式 |
|---|---|---|
| 动画令牌 | 统一管理时长和曲线 | AppAnimations 类 |
| animateTo | 状态变化动画 | 包裹状态更新代码 |
| 页面转场 | 页面间切换动画 | @EnterTransition / PageTransition |
| Loading | 加载中状态 | LoadingProgress + 条件渲染 |
| 触觉反馈 | 按键/长按反馈 | @ohos.vibrator |
| Skeleton | 骨架屏 | 纯 UI 组件填充占位 |
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
- HarmonyOS 动画开发指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-animation
- HarmonyOS 官方文档:https://developer.huawei.com/consumer/cn/doc/
- HarmonyOS hilog文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/hilog
- HarmonyOS testing:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/unit-test
- HarmonyOS 安全与权限:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/permissions-overview
更多推荐




所有评论(0)