0
点赞
收藏
分享

微信扫一扫

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)
  }

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

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

举报

相关推荐

0 条评论