🎯 一、HarmonyOS动画系统概述
HarmonyOS提供了强大而灵活的动画系统,支持多种动画类型和交互效果:
动画类型 | 适用场景 | 核心API |
---|---|---|
属性动画 | 组件尺寸、位置、透明度等属性变化 | animateTo() , Animation() |
转场动画 | 组件出现/消失、页面切换效果 | transition() , if/else 条件渲染 |
路径动画 | 复杂运动路径、曲线运动 | animator 模块 |
Lottie动画 | 复杂矢量动画、设计师制作动画 | Lottie 组件 |
粒子动画 | 特效、庆祝效果、自然现象 | Canvas 绘制 |
动画设计原则:
- 性能优先:确保动画流畅,帧率稳定在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)}@BuilderbuildViewA(): Column {Column() {Text('视图A').fontSize(24).fontColor(Color.Red)}}@BuilderbuildViewB(): 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()}}@BuilderbuildControlPanel(): 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)}}// 减少重绘区域@BuilderbuildPartialUpdateComponent() {Canvas(this.getContext()).onReady(() => {// 只更新需要变化的部分this.partialUpdate();})}// 使用transform代替top/left@BuilderbuildTransformOptimized() {Column() {// 使用translate而不是修改top/leftText('性能优化').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应用中创建丰富、流畅且高性能的动画效果,显著提升用户体验和应用品质。
需要参加鸿蒙认证的请点击 鸿蒙认证链接