当前位置: 首页 > news >正文

HarmonyOS 5 动画开发实战:从基础动效到高级交互动画

🎯 一、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应用中创建丰富、流畅且高性能的动画效果,显著提升用户体验和应用品质。

需要参加鸿蒙认证的请点击 鸿蒙认证链接

http://www.hskmm.com/?act=detail&tid=15957

相关文章:

  • HarmonyOS 5 高级动效实战:粒子系统、路径动画与物理动效开发
  • 从范德蒙德矩阵聊开去.
  • HarmonyOS 5 动画性能优化深度解析:从原理到实践
  • HarmonyOS 5 性能优化全攻略:从启动加速到内存管理
  • #字符串执行函数——eval()、exec()和compile()详解
  • HarmonyOS 5 网络编程与数据存储实战:从RESTful API到本地持久化
  • 【光照】[环境光ambient]以UnityURP为例
  • 浅谈当前时代下大学生的就业择业及人生规划
  • 实用指南:玳瑁的嵌入式日记---0923(ARM)
  • 个人博客搭建记录【hexo】
  • 喵喵喵
  • flink不同环境切换 - --
  • ps-填充色
  • HarmonyOS 5分布式数据同步实战:跨设备待办事项应用
  • 深入理解HarmonyOS 5的AVSession:构建跨设备媒体播放器
  • Extjs小例子
  • 匿名函数
  • HarmonyOS资源管理与访问:多分辨率与多语言适配
  • 面试官:为什么没有虚拟线程池?
  • 润生软件简介:以“重构与共生”引领商业未来
  • Python 并发编程
  • 安装pyautogui时与setuptool时冲突报错-module setuptools.dist has no attribute check_test_suite
  • 统计机器学习经典分类算法MATLAB实现
  • 从安装到中文界面,一文带你玩转 DaVinci Resolve 20(零基础也能搞定)
  • 靶场1
  • 299、已凉
  • linux手动安装阿里云Logtail采集Nginx访问日志
  • WPF的数据绑定之通知修改
  • 古代史
  • matlab运行时遇到的license问题