温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦!

HarmonyOS NEXT 动画系统详解:构建流畅的用户体验

1. 动画系统概述

1.1 动画类型

类型说明适用场景
属性动画改变组件属性值大小、位置、透明度变化
转场动画页面切换效果页面跳转、弹窗显示
手势动画跟随手势变化拖拽、滑动、缩放

1.2 动画属性

// 可动画化的属性
interface AnimatableProperties {
  opacity?: number;
  rotate?: number;
  scale?: number;
  translate?: [number, number];
  width?: number;
  height?: number;
  // ... 其他属性
}

2. 基础动画实现

2.1 属性动画

@Component
struct BasicAnimationDemo {
  @State private scale: number = 1;
  
  build() {
    Column() {
      Button('Animate')
        .animation({
          duration: 300,
          curve: Curve.EaseInOut,
          delay: 0,
          iterations: 1,
          playMode: PlayMode.Normal
        })
        .scale(this.scale)
        .onClick(() => {
          this.scale = this.scale === 1 ? 1.5 : 1;
        })
    }
  }
}

2.2 显式动画

@Component
struct ExplicitAnimationDemo {
  @State private opacity: number = 1;
  
  build() {
    Column() {
      Text('Fade Animation')
        .opacity(this.opacity)
        .onClick(() => {
          animateTo({
            duration: 500,
            curve: Curve.Smooth,
          }, () => {
            this.opacity = this.opacity === 1 ? 0 : 1;
          })
        })
    }
  }
}

3. 复杂动画处理

3.1 组合动画

@Component
struct CompositeAnimationDemo {
  @State private transform: {
    scale: number,
    rotate: number,
    translate: [number, number]
  } = {
    scale: 1,
    rotate: 0,
    translate: [0, 0]
  };
  
  build() {
    Column() {
      Image('icon.png')
        .transform(this.transform)
        .animation({
          duration: 500,
          curve: Curve.Smooth
        })
        .onClick(() => {
          this.transform = {
            scale: 1.2,
            rotate: 45,
            translate: [100, 100]
          };
        })
    }
  }
}

3.2 序列动画

class AnimationSequence {
  private animations: Array<() => Promise<void>> = [];
  
  addAnimation(animation: () => Promise<void>) {
    this.animations.push(animation);
  }
  
  async play() {
    for (const animation of this.animations) {
      await animation();
    }
  }
}

@Component
struct SequentialAnimationDemo {
  private sequence = new AnimationSequence();
  @State private position: number = 0;
  
  aboutToAppear() {
    this.sequence.addAnimation(() => 
      this.animate(() => this.position = 100));
    this.sequence.addAnimation(() => 
      this.animate(() => this.position = 200));
    this.sequence.addAnimation(() => 
      this.animate(() => this.position = 0));
  }
  
  private animate(update: () => void): Promise<void> {
    return new Promise(resolve => {
      animateTo({ duration: 500 }, () => {
        update();
        resolve();
      });
    });
  }
  
  build() {
    Column() {
      Button('Play Sequence')
        .onClick(() => this.sequence.play())
      
      Circle()
        .size({ width: 50, height: 50 })
        .offset({ x: this.position })
        .animation({
          duration: 500,
          curve: Curve.Smooth
        })
    }
  }
}

4. 性能优化

4.1 动画性能优化

@Component
struct PerformanceOptimizedAnimation {
  // 1. 使用transform代替位置属性
  @State private transform: {
    translate: [number, number]
  } = {
    translate: [0, 0]
  };
  
  // 2. 避免在动画中改变布局
  build() {
    Stack() {
      // 使用固定大小的容器
      Column()
        .width('100%')
        .height('100%')
      
      // 动画元素使用绝对定位
      Image('icon.png')
        .position({
          x: 0,
          y: 0
        })
        .transform(this.transform)
        .animation({
          duration: 300
        })
    }
  }
}

4.2 动画帧率优化

class AnimationOptimizer {
  private static readonly TARGET_FPS = 60;
  private static readonly FRAME_TIME = 1000 / this.TARGET_FPS;
  
  // 使用requestAnimationFrame优化动画
  static animate(
    duration: number,
    onFrame: (progress: number) => void,
    onComplete?: () => void
  ) {
    const startTime = Date.now();
    
    const update = () => {
      const currentTime = Date.now();
      const elapsed = currentTime - startTime;
      const progress = Math.min(elapsed / duration, 1);
      
      onFrame(progress);
      
      if (progress < 1) {
        requestAnimationFrame(update);
      } else if (onComplete) {
        onComplete();
      }
    };
    
    requestAnimationFrame(update);
  }
}

5. 实战案例

5.1 卡片翻转动画

@Component
struct CardFlipAnimation {
  @State private isFlipped: boolean = false;
  @State private rotateY: number = 0;
  
  build() {
    Stack() {
      // 正面
      Column()
        .width('100%')
        .height('100%')
        .backgroundColor('#FF0000')
        .opacity(this.rotateY <= 90 ? 1 : 0)
        .rotate({ y: this.rotateY })
      
      // 背面
      Column()
        .width('100%')
        .height('100%')
        .backgroundColor('#00FF00')
        .opacity(this.rotateY > 90 ? 1 : 0)
        .rotate({ y: this.rotateY - 180 })
    }
    .animation({
      duration: 500,
      curve: Curve.Smooth
    })
    .onClick(() => {
      this.rotateY = this.isFlipped ? 0 : 180;
      this.isFlipped = !this.isFlipped;
    })
  }
}

5.2 列表项动画

@Component
struct AnimatedListDemo {
  @State private items: Array<any> = [];
  
  build() {
    List() {
      ForEach(this.items, (item, index) => {
        ListItem() {
          Text(item.title)
            .opacity(0)
            .animation({
              delay: index * 100, // 错开动画
              duration: 300,
              curve: Curve.EaseOut
            })
            .onAppear(() => {
              animateTo({ duration: 300 }, () => {
                item.opacity = 1;
              })
            })
        }
      })
    }
  }
}

5.3 路由转场动画

@Entry
@Component
struct PageTransitionDemo {
  @State private currentPage: number = 1;
  
  build() {
    Stack() {
      if (this.currentPage === 1) {
        Page1()
          .transition({
            type: TransitionType.Push,
            direction: TransitionDirection.Left
          })
      } else {
        Page2()
          .transition({
            type: TransitionType.Push,
            direction: TransitionDirection.Left
          })
      }
    }
  }
}

5.4 最佳实践建议

  1. 性能优化

    • 使用transform代替位置属性
    • 避免动画过程中改变布局
    • 合理使用硬件加速
  2. 动画设计

    • 保持动画简单明确
    • 使用适当的缓动函数
    • 控制动画时长
  3. 用户体验

    • 提供适当的反馈
    • 保持动画流畅
    • 避免过度动画
  4. 代码组织

    • 封装可复用的动画
    • 使用动画管理器
    • 实现动画配置系统

通过合理使用动画系统,可以显著提升应用的用户体验。在实际开发中,要注意平衡动画效果和性能消耗,确保动画流畅自然,同时不影响应用的整体性能。


全栈若城
1 声望2 粉丝