HarmonyOS Next之动画实战:优雅实现弹窗动画

80 阅读2分钟

本文展示了HarmonyOS Next中四种弹窗动画的实现方案:1) 基础缩放动画,通过控制scale和opacity实现渐显效果;2) 底部滑动动画,使用translateY完成视差移动;3) 弹性动画,利用Spring曲线模拟物理弹性;4) 3D翻转动画,通过rotateY实现卡片翻转效果。每种方案都包含完整的代码示例,核心使用animateTo动画API配合状态管理,并详细说明了动画曲线选择、复合属性控制等关键技术点。这些实现方案覆盖了主流UI动画效果,可直接用于实际项目开发。

一、基础弹窗动画实现

// ScalePopup.ets
import { Curve, animateTo } from '@ohos.animation';

@Entry
@Component
struct ScalePopupExample {
  @State isShow: boolean = false;
  @State scale: number = 0.5;
  @State opacity: number = 0;

  // 显示弹窗动画
  showPopup() {
    this.isShow = true;
    animateTo({
      duration: 300,
      curve: Curve.EaseOut
    }, () => {
      this.scale = 1;
      this.opacity = 1;
    });
  }

  // 隐藏弹窗动画
  hidePopup() {
    animateTo({
      duration: 200,
      curve: Curve.EaseIn,
      onFinish: () => {
        this.isShow = false;
      }
    }, () => {
      this.scale = 0.5;
      this.opacity = 0;
    });
  }

  build() {
    Stack({ alignContent: Alignment.Center }) {
      // 主页面内容
      Column() {
        Button('显示弹窗')
          .onClick(() => this.showPopup())
          .margin(20)
      }
      
      // 弹窗层
      if (this.isShow) {
        // 半透明背景
        Column()
          .width('100%')
          .height('100%')
          .backgroundColor('#000000')
          .opacity(0.5 * this.opacity)
          .onClick(() => this.hidePopup())
        
        // 弹窗内容
        Column() {
          Text('HarmonyOS Next')
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
            .margin({ top: 20, bottom: 10 })
          
          Text('弹窗动画效果展示')
            .fontSize(18)
            .margin({ bottom: 20 })
          
          Button('关闭')
            .onClick(() => this.hidePopup())
            .width(120)
            .margin(10)
        }
        .width('80%')
        .padding(20)
        .backgroundColor(Color.White)
        .borderRadius(16)
        .shadow({ radius: 20, color: '#40000000' })
        .scale({ x: this.scale, y: this.scale })
        .opacity(this.opacity)
      }
    }
    .width('100%')
    .height('100%')
  }
}

关键点解析:

  1. 动画曲线:使用Curve.EaseOut实现平滑加速效果

  2. 状态管理:@State装饰器控制弹窗状态

  3. 复合动画:同时控制缩放和透明度

  4. 层级管理:使用Stack布局实现弹窗覆盖层

二、高级弹窗动画实现

// SlidePopup.ets
import { Curve, animateTo } from '@ohos.animation';

@Entry
@Component
struct SlidePopupExample {
  @State isShow: boolean = false;
  @State translateY: number = 1000; // 初始位置在屏幕外

  showPopup() {
    this.isShow = true;
    animateTo({
      duration: 400,
      curve: Curve.EaseOut
    }, () => {
      this.translateY = 0; // 滑入到屏幕底部
    });
  }

  hidePopup() {
    animateTo({
      duration: 300,
      curve: Curve.EaseIn,
      onFinish: () => {
        this.isShow = false;
        this.translateY = 1000; // 重置位置
      }
    }, () => {
      this.translateY = 1000; // 滑出屏幕
    });
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      // 主页面内容...
      
      // 底部弹窗
      if (this.isShow) {
        // 背景蒙层
        Column()
          .width('100%')
          .height('100%')
          .backgroundColor('#000000')
          .opacity(0.5)
          .onClick(() => this.hidePopup())
        
        // 弹窗内容
        Column() {
          // 内容区域...
        }
        .width('100%')
        .padding(20)
        .backgroundColor(Color.White)
        .borderRadius(16, 16, 0, 0)
        .shadow({ radius: 10, color: Color.Gray, offsetY: -5 })
        .translate({ y: this.translateY })
      }
    }
  }
}

弹性弹窗动画

// SpringPopup.ets
import { Curve, animateTo } from '@ohos.animation';

@Component
struct SpringPopup {
  @State scale: number = 0.8;
  @State opacity: number = 0;
  
  aboutToAppear() {
    // 弹性显示动画
    animateTo({
      duration: 600,
      curve: Curve.Spring({ 
        mass: 1, 
        stiffness: 400,
        damping: 15
      })
    }, () => {
      this.scale = 1;
      this.opacity = 1;
    });
  }

  build() {
    Column() {
      // 弹窗内容...
    }
    .scale({ x: this.scale, y: this.scale })
    .opacity(this.opacity)
  }
}

三、3D翻转弹窗动画

// FlipPopup.ets
import { Curve, animateTo } from '@ohos.animation';

@Entry
@Component
struct FlipPopupExample {
  @State isShow: boolean = false;
  @State angleY: number = 90; // 初始90度(不可见)
  @State isFront: boolean = true;

  // 3D翻转动画
  flipPopup() {
    const targetAngle = this.isFront ? 270 : 90;
    
    animateTo({
      duration: 800,
      curve: Curve.EaseInOut,
      onFinish: () => {
        this.isFront = !this.isFront;
      }
    }, () => {
      this.angleY = targetAngle;
    });
  }

  build() {
    Stack() {
      // 主页面内容...
      
      if (this.isShow) {
        // 3D容器
        Stack({ alignContent: Alignment.Center }) {
          // 正面
          Column() {
            // 正面内容...
          }
          .rotate({ y: this.angleY })
          .opacity(this.isFront ? 1 : 0)
          
          // 背面
          Column() {
            // 背面内容...
          }
          .rotate({ y: this.angleY - 180 })
          .opacity(this.isFront ? 0 : 1)
        }
        .perspective(1000)
        .onClick(() => this.flipPopup())
      }
    }
  }
}