🎯 一、HarmonyOS动画系统概述
HarmonyOS提供了强大而灵活的动画系统,支持多种动画类型和交互效果:
动画类型 | 适用场景 | 核心API |
属性动画 | 组件尺寸、位置、透明度等属性变化 |
|
转场动画 | 组件出现/消失、页面切换效果 |
|
路径动画 | 复杂运动路径、曲线运动 |
|
Lottie动画 | 复杂矢量动画、设计师制作动画 |
|
粒子动画 | 特效、庆祝效果、自然现象 |
|
动画设计原则:
- 性能优先:确保动画流畅,帧率稳定在60fps
- 用户体验:动画应有明确目的,增强而非干扰用户体验
- 一致性:保持应用内动画风格一致
- 可访问性:提供减少动画或关闭动画的选项
⚙️ 二、开发准备与配置
1. 模块导入
在ArkTS文件中导入必要的动画模块:
import animator from '@ohos.animator';
import lottie from '@ohos.lottie';
import canvas from '@ohos.graphics.canvas';
import { Curve, FillMode, PlayMode } from '@ohos.animator';
2. 基础动画组件配置
在 module.json5
中配置必要的资源:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.GRAPHICS",
"reason": "$string:graphics_permission_reason",
"usedScene": {
"abilities": ["MainAbility"],
"when": "always"
}
}
],
"abilities": [
{
"name": "MainAbility",
"srcEntry": "./ets/MainAbility/MainAbility.ets",
"animation": {
"entrance": "fade_in",
"exit": "fade_out"
}
}
]
}
}
🧩 三、属性动画开发
1. 基础属性动画
使用 animateTo
实现简单的属性变化动画:
@Component
struct ScaleAnimationExample {
@State scaleValue: number = 1.0;
@State opacityValue: number = 1.0;
@State rotationValue: number = 0;
build() {
Column() {
// 缩放动画
Text('缩放动画')
.fontSize(20)
.scale({ x: this.scaleValue, y: this.scaleValue })
.onClick(() => {
animateTo({
duration: 300,
curve: Curve.EaseInOut
}, () => {
this.scaleValue = this.scaleValue === 1.0 ? 1.5 : 1.0;
});
})
// 透明度动画
Text('淡入淡出')
.fontSize(20)
.opacity(this.opacityValue)
.margin({ top: 20 })
.onClick(() => {
animateTo({
duration: 500,
curve: Curve.EaseIn
}, () => {
this.opacityValue = this.opacityValue === 1.0 ? 0.3 : 1.0;
});
})
// 旋转动画
Text('旋转动画')
.fontSize(20)
.rotate({ angle: this.rotationValue })
.margin({ top: 20 })
.onClick(() => {
animateTo({
duration: 600,
curve: Curve.EaseOut
}, () => {
this.rotationValue += 45;
});
})
}
.width('100%')
.height('100%')
.padding(20)
}
}
2. 高级动画配置
使用 Animation
构造函数进行更精细的动画控制:
@Component
struct AdvancedAnimationExample {
@State translateX: number = 0;
@State translateY: number = 0;
private animationController: animator.AnimatorResult | null = null;
// 创建复杂动画
createComplexAnimation(): void {
const options: animator.AnimatorOptions = {
duration: 1000,
delay: 200,
curve: Curve.Spring,
iterations: 3,
fillMode: FillMode.Forwards,
direction: PlayMode.Alternate
};
this.animationController = animateTo(options, () => {
this.translateX = 100;
this.translateY = 50;
});
}
// 控制动画播放
controlAnimation(): void {
if (this.animationController) {
// 暂停动画
this.animationController.pause();
// 继续播放
setTimeout(() => {
this.animationController?.resume();
}, 1000);
}
}
build() {
Column() {
Text('高级动画控制')
.fontSize(20)
.translate({ x: this.translateX, y: this.translateY })
.onClick(() => {
this.createComplexAnimation();
})
Button('暂停/继续')
.onClick(() => {
this.controlAnimation();
})
.margin({ top: 20 })
}
}
}
🔄 四、转场动画与页面过渡
1. 组件显隐转场
使用 transition
实现平滑的组件出现和消失效果:
@Component
struct TransitionAnimationExample {
@State isVisible: boolean = true;
@State currentView: string = 'viewA';
build() {
Column() {
// 条件渲染与转场
if (this.isVisible) {
Text('我会优雅地出现和消失')
.fontSize(18)
.padding(16)
.backgroundColor(Color.Blue)
.transition({
type: TransitionType.Insert,
opacity: 0,
translate: { x: 0, y: -100 },
scale: { x: 0.5, y: 0.5 }
})
.transition({
type: TransitionType.Delete,
opacity: 0,
translate: { x: 0, y: 100 },
scale: { x: 0.5, y: 0.5 }
})
}
Button(this.isVisible ? '隐藏' : '显示')
.onClick(() => {
this.isVisible = !this.isVisible;
})
.margin({ top: 20 })
// 视图切换动画
if (this.currentView === 'viewA') {
this.buildViewA()
.transition({ type: TransitionType.Insert, opacity: 0 })
.transition({ type: TransitionType.Delete, opacity: 0 })
} else {
this.buildViewB()
.transition({ type: TransitionType.Insert, opacity: 0 })
.transition({ type: TransitionType.Delete, opacity: 0 })
}
Button('切换视图')
.onClick(() => {
this.currentView = this.currentView === 'viewA' ? 'viewB' : 'viewA';
})
}
.padding(20)
}
@Builder
buildViewA(): Column {
Column() {
Text('视图A')
.fontSize(24)
.fontColor(Color.Red)
}
}
@Builder
buildViewB(): Column {
Column() {
Text('视图B')
.fontSize(24)
.fontColor(Color.Green)
}
}
}
2. 页面间转场动画
实现自定义的页面切换效果:
@Entry
@Component
struct PageTransitionExample {
@State currentPage: string = 'home';
@State transitionDirection: string = 'forward';
build() {
Stack() {
if (this.currentPage === 'home') {
HomePage()
.transition(this.getPageTransition())
} else if (this.currentPage === 'detail') {
DetailPage()
.transition(this.getPageTransition())
} else if (this.currentPage === 'profile') {
ProfilePage()
.transition(this.getPageTransition())
}
}
.width('100%')
.height('100%')
}
// 获取页面转场效果
private getPageTransition(): TransitionEffect {
switch (this.transitionDirection) {
case 'forward':
return TransitionEffect.OPACITY
.combine(TransitionEffect.translate({ x: 100, y: 0 }))
.animation({ duration: 300, curve: Curve.EaseOut });
case 'backward':
return TransitionEffect.OPACITY
.combine(TransitionEffect.translate({ x: -100, y: 0 }))
.animation({ duration: 300, curve: Curve.EaseOut });
default:
return TransitionEffect.OPACITY
.animation({ duration: 300 });
}
}
// 导航到新页面
navigateTo(page: string, direction: string = 'forward'): void {
this.transitionDirection = direction;
this.currentPage = page;
}
}
// 主页组件
@Component
struct HomePage {
build() {
Column() {
Text('主页')
.fontSize(30)
Button('前往详情页')
.onClick(() => {
// 获取上下文并导航
const context = getContext(this) as common.UIAbilityContext;
// 实际项目中这里会使用router跳转
})
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
🎞️ 五、Lottie动画集成
1. 基础Lottie动画
集成和使用Lottie矢量动画:
@Component
struct LottieAnimationExample {
private lottieController: lottie.LottieController = new lottie.LottieController();
@State isPlaying: boolean = true;
build() {
Column() {
// Lottie动画组件
lottie.Lottie({
controller: this.lottieController,
src: $rawfile('animations/celebrate.json'),
autoPlay: true,
loop: true
})
.width(200)
.height(200)
.onClick(() => {
this.toggleAnimation();
})
// 动画控制按钮
Row() {
Button(this.isPlaying ? '暂停' : '播放')
.onClick(() => {
this.toggleAnimation();
})
Button('重播')
.onClick(() => {
this.restartAnimation();
})
.margin({ left: 12 })
}
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.padding(20)
}
// 切换动画状态
private toggleAnimation(): void {
if (this.isPlaying) {
this.lottieController.pause();
} else {
this.lottieController.play();
}
this.isPlaying = !this.isPlaying;
}
// 重启动画
private restartAnimation(): void {
this.lottieController.reset();
this.lottieController.play();
this.isPlaying = true;
}
}
2. 交互式Lottie动画
实现与用户交互联动的Lottie动画:
@Component
struct InteractiveLottieExample {
private lottieController: lottie.LottieController = new lottie.LottieController();
@State progress: number = 0;
@State isInteractive: boolean = false;
build() {
Column() {
// 可交互的Lottie动画
lottie.Lottie({
controller: this.lottieController,
src: $rawfile('animations/interactive.json'),
progress: this.progress,
autoPlay: false
})
.width(300)
.height(300)
.gesture(
GestureGroup(GestureMode.Parallel,
// 拖拽手势控制进度
PanGesture()
.onActionUpdate((event: GestureEvent) => {
if (this.isInteractive) {
const newProgress = Math.max(0, Math.min(1, this.progress + event.offsetX / 300));
this.progress = newProgress;
this.lottieController.setProgress(newProgress);
}
})
)
)
// 进度控制滑块
Slider({ value: this.progress, min: 0, max: 1 })
.onChange((value: number) => {
this.progress = value;
this.lottieController.setProgress(value);
})
.width('80%')
.margin({ top: 20 })
Toggle({ isOn: this.isInteractive })
.onChange((isOn: boolean) => {
this.isInteractive = isOn;
})
.margin({ top: 12 })
Text('交互模式: ' + (this.isInteractive ? '开启' : '关闭'))
}
.padding(20)
}
}
✨ 六、粒子动画与特效
1. 基础粒子系统
使用Canvas创建粒子动画效果:
@Component
struct ParticleAnimationExample {
@State particles: Array<{x: number, y: number, size: number, color: string, velocity: {x: number, y: number}}> = [];
private animationFrame: number = 0;
build() {
Canvas(this.getContext())
.width('100%')
.height('100%')
.backgroundColor(Color.Black)
.onReady(() => {
this.initParticles();
this.startAnimation();
})
.onDisappear(() => {
this.stopAnimation();
})
}
// 获取Canvas上下文
private getContext(): CanvasRenderingContext2D {
const context = new CanvasRenderingContext2D();
return context;
}
// 初始化粒子
private initParticles(): void {
const colors = ['#FF5252', '#FF4081', '#E040FB', '#7C4DFF', '#536DFE'];
for (let i = 0; i < 100; i++) {
this.particles.push({
x: Math.random() * 360,
y: Math.random() * 640,
size: Math.random() * 5 + 1,
color: colors[Math.floor(Math.random() * colors.length)],
velocity: {
x: (Math.random() - 0.5) * 2,
y: (Math.random() - 0.5) * 2
}
});
}
}
// 开始动画循环
private startAnimation(): void {
const animate = () => {
this.updateParticles();
this.drawParticles();
this.animationFrame = requestAnimationFrame(animate);
};
this.animationFrame = requestAnimationFrame(animate);
}
// 停止动画
private stopAnimation(): void {
if (this.animationFrame) {
cancelAnimationFrame(this.animationFrame);
}
}
// 更新粒子状态
private updateParticles(): void {
this.particles.forEach(particle => {
particle.x += particle.velocity.x;
particle.y += particle.velocity.y;
// 边界检查
if (particle.x < 0 || particle.x > 360) {
particle.velocity.x *= -1;
}
if (particle.y < 0 || particle.y > 640) {
particle.velocity.y *= -1;
}
});
}
// 绘制粒子
private drawParticles(): void {
const context = this.getContext();
context.clearRect(0, 0, 360, 640);
this.particles.forEach(particle => {
context.beginPath();
context.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
context.fillStyle = particle.color;
context.fill();
});
}
}
2. 高级粒子效果
创建复杂的粒子特效系统:
@Component
struct AdvancedParticleSystem {
@State emitterPosition: { x: number, y: number } = { x: 180, y: 320 };
@State particles: Particle[] = [];
private particleId: number = 0;
build() {
Stack() {
// 粒子画布
Canvas(this.getContext())
.width('100%')
.height('100%')
.onReady(() => {
this.startEmission();
})
// 发射器控制
Column() {
Slider({ value: this.emitterPosition.x, min: 0, max: 360 })
.onChange((value: number) => {
this.emitterPosition.x = value;
})
Slider({ value: this.emitterPosition.y, min: 0, max: 640 })
.onChange((value: number) => {
this.emitterPosition.y = value;
})
}
.position({ x: 0, y: 500 })
}
}
// 开始粒子发射
private startEmission(): void {
setInterval(() => {
this.emitParticle();
}, 100);
// 动画循环
const animate = () => {
this.updateParticles();
this.drawParticles();
requestAnimationFrame(animate);
};
animate();
}
// 发射单个粒子
private emitParticle(): void {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 3 + 1;
this.particles.push({
id: this.particleId++,
x: this.emitterPosition.x,
y: this.emitterPosition.y,
size: Math.random() * 8 + 2,
color: this.getRandomColor(),
velocity: {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed
},
life: 1.0,
decay: Math.random() * 0.02 + 0.01
});
}
// 更新粒子状态
private updateParticles(): void {
this.particles = this.particles.filter(particle => {
particle.x += particle.velocity.x;
particle.y += particle.velocity.y;
particle.life -= particle.decay;
return particle.life > 0;
});
}
// 绘制粒子
private drawParticles(): void {
const context = this.getContext();
context.clearRect(0, 0, 360, 640);
this.particles.forEach(particle => {
context.globalAlpha = particle.life;
context.beginPath();
context.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
context.fillStyle = particle.color;
context.fill();
});
context.globalAlpha = 1.0;
}
// 获取随机颜色
private getRandomColor(): string {
const hues = [0, 60, 120, 180, 240, 300]; // 红、黄、绿、青、蓝、紫
return `hsl(${hues[Math.floor(Math.random() * hues.length)]}, 100%, 60%)`;
}
}
interface Particle {
id: number;
x: number;
y: number;
size: number;
color: string;
velocity: { x: number; y: number };
life: number;
decay: number;
}
🎮 七、交互反馈动画
1. 按钮交互效果
创建具有丰富反馈的交互按钮:
@Component
struct InteractiveButtonExample {
@State isPressed: boolean = false;
@State isHovered: boolean = false;
build() {
Column() {
// 交互式按钮
Button('交互按钮')
.width(200)
.height(60)
.backgroundColor(this.getButtonColor())
.scale(this.isPressed ? 0.95 : 1.0)
.opacity(this.isHovered ? 0.9 : 1.0)
.stateStyles({
pressed: {
backgroundColor: Color.Gray
},
normal: {
backgroundColor: Color.Blue
}
})
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.isPressed = true;
} else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
this.isPressed = false;
}
})
.onHover((isHovered: boolean) => {
this.isHovered = isHovered;
})
.animation({
duration: 150,
curve: Curve.EaseOut
})
// 点赞动画按钮
LikeButton()
.margin({ top: 30 })
}
.padding(20)
}
private getButtonColor(): Color {
if (this.isPressed) return Color.Gray;
if (this.isHovered) return Color.Blue;
return Color.Red;
}
}
// 点赞按钮组件
@Component
struct LikeButton {
@State isLiked: boolean = false;
@State scaleValue: number = 1.0;
@State rotationValue: number = 0;
build() {
Image(this.isLiked ? $r('app.media.ic_liked') : $r('app.media.ic_like'))
.width(40)
.height(40)
.scale({ x: this.scaleValue, y: this.scaleValue })
.rotate({ angle: this.rotationValue })
.onClick(() => {
this.animateLike();
})
}
private animateLike(): void {
if (!this.isLiked) {
// 点赞动画序列
animateTo({ duration: 100, curve: Curve.EaseOut }, () => {
this.scaleValue = 1.2;
});
animateTo({ duration: 100, curve: Curve.EaseIn }, () => {
this.scaleValue = 0.9;
this.rotationValue = -15;
});
animateTo({ duration: 200, curve: Curve.EaseOut }, () => {
this.scaleValue = 1.0;
this.rotationValue = 0;
this.isLiked = true;
});
} else {
// 取消点赞动画
animateTo({ duration: 200, curve: Curve.EaseInOut }, () => {
this.scaleValue = 0.8;
this.isLiked = false;
});
animateTo({ duration: 200, curve: Curve.EaseOut }, () => {
this.scaleValue = 1.0;
});
}
}
}
2. 手势驱动动画
创建基于手势的交互动画:
@Component
struct GestureDrivenAnimation {
@State offsetX: number = 0;
@State offsetY: number = 0;
@State scale: number = 1.0;
@State rotation: number = 0;
@State isDragging: boolean = false;
build() {
Stack() {
// 可拖动、缩放、旋转的组件
Image($r('app.media.draggable_image'))
.width(200)
.height(200)
.translate({ x: this.offsetX, y: this.offsetY })
.scale({ x: this.scale, y: this.scale })
.rotate({ angle: this.rotation })
.gesture(
GestureGroup(GestureMode.Parallel,
// 拖拽手势
PanGesture({ fingers: 1 })
.onActionUpdate((event: GestureEvent) => {
this.offsetX += event.offsetX;
this.offsetY += event.offsetY;
this.isDragging = true;
})
.onActionEnd(() => {
this.isDragging = false;
// 归位动画
animateTo({ duration: 300, curve: Curve.Spring }, () => {
this.offsetX = 0;
this.offsetY = 0;
});
}),
// 缩放手势
PinchGesture({ fingers: 2 })
.onActionUpdate((event: GestureEvent) => {
this.scale *= event.scale;
this.scale = Math.max(0.5, Math.min(3, this.scale));
}),
// 旋转手势
RotateGesture({ fingers: 2 })
.onActionUpdate((event: GestureEvent) => {
this.rotation += event.angle;
})
)
)
.animation({
duration: this.isDragging ? 0 : 300,
curve: Curve.EaseOut
})
// 控制面板
this.buildControlPanel()
}
}
@Builder
buildControlPanel(): Column {
Column() {
Slider({ value: this.scale, min: 0.5, max: 3.0 })
.onChange((value: number) => {
this.scale = value;
})
Slider({ value: this.rotation, min: -180, max: 180 })
.onChange((value: number) => {
this.rotation = value;
})
Button('重置')
.onClick(() => {
animateTo({ duration: 500, curve: Curve.Spring }, () => {
this.offsetX = 0;
this.offsetY = 0;
this.scale = 1.0;
this.rotation = 0;
});
})
}
.position({ x: 0, y: 400 })
}
}
⚡ 八、性能优化与最佳实践
1. 动画性能优化
确保动画流畅运行的优化策略:
@Component
struct OptimizedAnimationExample {
private heavyOperationWorker: worker.Worker | null = null;
aboutToAppear() {
// 在Web Worker中执行复杂计算
this.heavyOperationWorker = new worker.Worker('workers/AnimationWorker.ts');
}
// 使用willChange提示浏览器优化
build() {
Column() {
Text('优化动画')
.fontSize(20)
.willChange(WillChange.Transform, WillChange.Opacity)
.scale({ x: this.scaleValue, y: this.scaleValue })
.opacity(this.opacityValue)
}
}
// 减少重绘区域
@Builder
buildPartialUpdateComponent() {
Canvas(this.getContext())
.onReady(() => {
// 只更新需要变化的部分
this.partialUpdate();
})
}
// 使用transform代替top/left
@Builder
buildTransformOptimized() {
Column() {
// 使用translate而不是修改top/left
Text('性能优化')
.translate({ x: this.offsetX, y: this.offsetY }) // 使用GPU加速
}
}
aboutToDisappear() {
// 清理资源
if (this.heavyOperationWorker) {
this.heavyOperationWorker.terminate();
}
}
}
2. 内存管理
动画相关的内存管理最佳实践:
class AnimationMemoryManager {
private static animationCache: Map<string, animator.AnimatorResult> = new Map();
private static activeAnimations: Set<animator.AnimatorResult> = new Set();
// 缓存动画对象
static cacheAnimation(key: string, animation: animator.AnimatorResult): void {
this.animationCache.set(key, animation);
}
// 获取缓存的动画
static getCachedAnimation(key: string): animator.AnimatorResult | undefined {
return this.animationCache.get(key);
}
// 跟踪活动动画
static trackAnimation(animation: animator.AnimatorResult): void {
this.activeAnimations.add(animation);
}
// 清理不再使用的动画
static cleanupUnusedAnimations(): void {
for (const animation of this.activeAnimations) {
if (animation.isFinished()) {
animation.destroy();
this.activeAnimations.delete(animation);
}
}
}
// 紧急停止所有动画
static emergencyStopAllAnimations(): void {
for (const animation of this.activeAnimations) {
animation.stop();
animation.destroy();
}
this.activeAnimations.clear();
this.animationCache.clear();
}
}
🧪 九、测试与调试
1. 动画调试工具
创建动画调试和性能分析工具:
@Component
struct AnimationDebugger {
@State fps: number = 0;
@State memoryUsage: number = 0;
private frameCount: number = 0;
private lastTime: number = 0;
build() {
Column() {
// 调试信息面板
Text(`FPS: ${this.fps}`)
.fontSize(14)
.fontColor(Color.Red)
Text(`内存: ${this.memoryUsage}MB`)
.fontSize(14)
.fontColor(Color.Red)
Text('动画调试面板')
.fontSize(16)
.margin({ top: 10 })
// 动画测试区域
AnimationTestZone()
}
.onAppear(() => {
this.startPerformanceMonitoring();
})
.onDisappear(() => {
this.stopPerformanceMonitoring();
})
}
// 性能监控
private startPerformanceMonitoring(): void {
const monitor = () => {
const now = Date.now();
this.frameCount++;
if (now - this.lastTime >= 1000) {
this.fps = Math.round((this.frameCount * 1000) / (now - this.lastTime));
this.memoryUsage = performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1048576) : 0;
this.frameCount = 0;
this.lastTime = now;
}
requestAnimationFrame(monitor);
};
this.lastTime = Date.now();
requestAnimationFrame(monitor);
}
private stopPerformanceMonitoring(): void {
// 清理监控资源
}
}
通过掌握这些动画技术,你可以在HarmonyOS应用中创建丰富、流畅且高性能的动画效果,显著提升用户体验和应用品质。
需要参加鸿蒙认证的请点击 鸿蒙认证链接