HarmonyOS鸿蒙Next中怎样给组件添加显隐动画?

HarmonyOS鸿蒙Next中怎样给组件添加显隐动画? 如何实现流畅的页面转场动画?
如何给组件添加显隐动画?
如何实现数字变化的动画效果?

4 回复

666

更多关于HarmonyOS鸿蒙Next中怎样给组件添加显隐动画?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


实现代码

/**
 * 组件显隐动画
 */
@Component
struct AnimationDemo {
  [@State](/user/State) visible: boolean = false;
  
  build() {
    Column({ space: 20 }) {
      Button('切换显示')
        .onClick(() => {
          animateTo({
            duration: 300,
            curve: Curve.EaseInOut
          }, () => {
            this.visible = !this.visible;
          })
        })
      
      if (this.visible) {
        Column() {
          Text('动画内容')
            .fontSize(18)
        }
        .width(200)
        .height(100)
        .backgroundColor('#ff6b6b')
        .borderRadius(12)
        .transition({
          type: TransitionType.Insert,
          opacity: 0,
          translate: { y: -50 }
        })
        .transition({
          type: TransitionType.Delete,
          opacity: 0,
          translate: { y: 50 }
        })
      }
    }
    .padding(20)
  }
}

/**
 * 数字变化动画
 */
@Component
struct NumberAnimationDemo {
  [@State](/user/State) number: number = 0;
  [@State](/user/State) displayNumber: number = 0;
  private timer: number = -1;
  
  animateNumber(target: number) {
    const start = this.displayNumber;
    const diff = target - start;
    const duration = 1000;
    const steps = 60;
    const stepValue = diff / steps;
    let currentStep = 0;
    
    if (this.timer >= 0) {
      clearInterval(this.timer);
    }
    
    this.timer = setInterval(() => {
      currentStep++;
      if (currentStep >= steps) {
        this.displayNumber = target;
        clearInterval(this.timer);
        this.timer = -1;
      } else {
        this.displayNumber = start + stepValue * currentStep;
      }
    }, duration / steps);
  }
  
  aboutToDisappear() {
    if (this.timer >= 0) {
      clearInterval(this.timer);
    }
  }
  
  build() {
    Column({ space: 20 }) {
      Text(`¥${this.displayNumber.toFixed(2)}`)
        .fontSize(48)
        .fontWeight(FontWeight.Bold)
        .fontColor('#ff6b6b')
      
      Row({ space: 12 }) {
        Button('增加1000')
          .onClick(() => {
            this.number += 1000;
            this.animateNumber(this.number);
          })
        
        Button('减少500')
          .onClick(() => {
            this.number -= 500;
            this.animateNumber(this.number);
          })
      }
    }
    .padding(20)
  }
}

/**
 * 列表项动画
 */
@Component
struct ListItemAnimation {
  [@State](/user/State) items: string[] = ['项目1', '项目2', '项目3'];
  
  build() {
    Column({ space: 12 }) {
      Button('添加项目')
        .onClick(() => {
          animateTo({ duration: 300 }, () => {
            this.items.push(`项目${this.items.length + 1}`);
          })
        })
      
      List({ space: 8 }) {
        ForEach(this.items, (item: string, index: number) => {
          ListItem() {
            Row() {
              Text(item)
                .fontSize(16)
                .layoutWeight(1)
              
              Button('删除')
                .fontSize(14)
                .backgroundColor('#ff6b6b')
                .onClick(() => {
                  animateTo({ duration: 300 }, () => {
                    this.items.splice(index, 1);
                  })
                })
            }
            .width('100%')
            .padding(16)
            .backgroundColor(Color.White)
            .borderRadius(8)
          }
          .transition({
            type: TransitionType.All,
            opacity: 0,
            translate: { x: -100 }
          })
        })
      }
      .layoutWeight(1)
    }
    .padding(16)
    .width('100%')
    .height('100%')
  }
}

/**
 * 旋转加载动画
 */
@Component
struct RotateAnimation {
  [@State](/user/State) angle: number = 0;
  private timer: number = -1;
  
  startRotate() {
    this.timer = setInterval(() => {
      animateTo({ duration: 1000, curve: Curve.Linear }, () => {
        this.angle += 360;
      })
    }, 1000);
  }
  
  stopRotate() {
    if (this.timer >= 0) {
      clearInterval(this.timer);
      this.timer = -1;
    }
  }
  
  aboutToAppear() {
    this.startRotate();
  }
  
  aboutToDisappear() {
    this.stopRotate();
  }
  
  build() {
    Column() {
      Image($r('app.media.icon'))
        .width(50)
        .height(50)
        .rotate({ angle: this.angle })
    }
  }
}

使用示例

// 使用组件动画
animateTo({ duration: 300, curve: Curve.EaseInOut }, () => {
  this.visible = !this.visible;
});

// 使用数字动画
this.animateNumber(5000);

// 列表添加动画
animateTo({ duration: 300 }, () => {
  this.items.push('新项目');
});

原理解析

1. animateTo闭包动画

animateTo({ duration: 300, curve: Curve.EaseInOut }, () => {
  this.visible = !this.visible; // 状态变化会触发动画
})
  • 闭包内的状态变化会产生动画
  • duration指定动画时长(毫秒)
  • curve指定动画曲线

2. transition转场

.transition({
  type: TransitionType.Insert,
  opacity: 0,
  translate: { y: -50 }
})
  • Insert:组件插入时的动画
  • Delete:组件删除时的动画
  • All:插入和删除都使用相同动画

3. 数字动画原理

  • 使用setInterval逐帧更新数字
  • 计算每帧的增量(目标值-当前值)/帧数
  • 达到目标值后清除定时器
  • 组件销毁时必须清除定时器

4. 常用动画曲线

  • Curve.Linear:线性,匀速
  • Curve.EaseInOut:先加速后减速,最自然
  • Curve.Friction:摩擦力,有弹性
  • Curve.Sharp:快速开始和结束

最佳实践

  1. 动画时长: 通常使用300ms,过长会显得拖沓
  2. 动画曲线: EaseInOut最自然,Friction有弹性效果
  3. 性能: 避免同时执行大量动画
  4. 状态管理: 动画相关状态用@State
  5. 清理资源: 组件销毁时清除定时器

避坑指南

  1. 忘记animateTo: 直接修改状态不会有动画
  2. transition位置: transition要放在组件上,不是容器
  3. 定时器泄漏: 忘记clearInterval导致内存泄漏
  4. 动画冲突: 同一属性不要同时执行多个动画
  5. 性能问题: 列表项过多时避免使用transition

在HarmonyOS Next中,使用显隐动画可通过组件的transition方法实现。通过设置组件的opacity属性,结合animateTo函数,可以控制组件的淡入淡出效果。例如,使用animateTo改变opacity从0到1实现显示动画,从1到0实现隐藏动画。

在HarmonyOS Next中,给组件添加显隐动画主要通过ArkUI的显示动画接口实现,核心是使用animateTo函数与组件状态绑定。

1. 显隐动画实现方式:

// 通过状态控制显隐并添加动画
@State isVisible: boolean = true

animateTo({
  duration: 300,  // 动画时长
  curve: Curve.EaseInOut  // 动画曲线
}, () => {
  this.isVisible = !this.isVisible  // 切换状态
})

2. 组件代码示例:

if (this.isVisible) {
  Text('显示/隐藏的文本')
    .opacity(this.isVisible ? 1 : 0)  // 透明度变化
    .scale({ x: this.isVisible ? 1 : 0.5, y: this.isVisible ? 1 : 0.5 }) // 缩放效果
}

3. 页面转场动画: 使用Navigation或自定义转场:

// 页面跳转带动画
router.pushUrl({
  url: 'pages/NextPage',
  params: { data: 'test' }
}, router.RouterMode.Standard, (err) => {
  // 转场完成回调
})

4. 数字变化动画:

@State count: number = 0

// 数字递增动画
animateTo({
  duration: 1000
}, () => {
  this.count += 100
})

关键点:

  • 使用@State装饰器管理动画状态
  • animateTo支持duration、curve、delay等参数配置
  • 可组合opacity、scale、translate等变换属性
  • 系统提供多种预置曲线:Ease、Linear、Spring等

通过状态驱动UI更新的机制,配合animateTo动画函数,能够实现流畅的组件显隐、页面转场和数值动画效果。

回到顶部