一、前言

前面系列教程完成了鸿蒙 UI 组件、路由、本地存储、网络请求、自定义弹窗、RDB 数据库、动态权限全栈基础开发,而流畅动画是提升 APP 质感、优化用户交互体验的关键

原生 ArkUI 内置高性能动画能力,无需依赖第三方库,支持四大核心动画场景:

  1. 属性动画(animateAttr):宽高、位移、透明度、圆角、缩放渐变,最常用组件动画
  2. 显式动画(animateTo):状态变量驱动动画,一行代码实现过渡动效,极简易用
  3. 弹窗入场 / 退场动画:搭配前文 CustomDialog,定制弹窗弹出收起动画
  4. 页面路由转场动画:页面跳转自定义切换动画,替换系统默认生硬切换

本文全部案例贴合实际项目,补齐整套鸿蒙开发教程最后一块交互短板,所有代码兼容最新 OpenHarmony 4.0/5.0 版本,可直接复制运行。

二、ArkUI 动画核心基础概念

2.1 动画通用参数说明

  • duration:动画时长,单位 ms,建议 200-500ms,符合人体视觉习惯
  • curve:动画插值曲线(线性、缓入、缓出、弹性曲线)
  • delay:动画延迟执行时间
  • iterations:动画循环次数,-1 代表无限循环
  • playMode:动画播放模式,正向 / 反向 / 交替播放

2.2 两类动画核心区别(开发必记)

  • animateTo 显式动画:操作简单,修改状态变量自动生成过渡动画,适合按钮点击、弹窗显隐、数值变化
  • animateAttr 属性动画:细粒度更高,直接操作组件样式属性,无需状态变量,适合悬浮动画、呼吸灯、图标微动

三、入门实战:animateTo 显式动画(项目最常用)

核心逻辑:把状态修改代码放入 animateTo 回调,系统自动生成平滑过渡动画,零基础上手最简单。

3.1 基础案例:组件缩放 + 位移 + 透明度动画

ets

@Entry
@Component
struct AnimateToBasicDemo {
  // 绑定动画状态变量
  @State scaleSize: number = 1
  @State offsetX: number = 0
  @State opacityValue: number = 1

  startAnim() {
    // 开启显式动画
    animateTo({
      duration: 300, // 动画时长300ms
      curve: Curve.EaseOut, // 先快后慢,贴合原生系统动画节奏
      iterations: 1,
      playMode: PlayMode.Normal
    }, () => {
      // 此处修改所有状态变量,自动附带过渡动画
      this.scaleSize = 1.5
      this.offsetX = 80
      this.opacityValue = 0.4
    })
  }

  resetAnim() {
    animateTo({duration: 300}, () => {
      this.scaleSize = 1
      this.offsetX = 0
      this.opacityValue = 1
    })
  }

  build() {
    Column({space:30}) {
      // 带动画的方块组件
      Rect()
        .width(100)
        .height(100)
        .fillColor('#007DFF')
        .scale({x:this.scaleSize, y:this.scaleSize})
        .offset({x:this.offsetX})
        .opacity(this.opacityValue)

      Row({space:20}) {
        Button("开启动画").onClick(()=>this.startAnim())
        Button("重置动画").onClick(()=>this.resetAnim())
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

3.2 高阶案例:无限循环呼吸动画(图标常驻微动)

适用于首页 logo、加载图标、状态提示图标,无限循环淡入淡出呼吸效果

ets

@Entry
@Component
struct BreatheAnimDemo {
  @State opacity: number = 0.6

  aboutToAppear() {
    // 页面加载自动开启无限呼吸动画
    animateTo({
      duration: 1500,
      curve: Curve.EaseInOut,
      iterations: -1, // 无限循环
      playMode: PlayMode.Alternate // 往返播放
    }, () => {
      this.opacity = 1
    })
  }

  build() {
    Column() {
      Image($r('app.media.icon'))
        .width(120)
        .opacity(this.opacity)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

四、进阶实战:animateAttr 属性动画(无状态变量动画)

无需定义 @State 状态变量,直接绑定组件,精准控制单一组件样式变化,适合复杂连续动画、悬浮按钮动效。

4.1 悬浮按钮上下浮动动画

ets

@Entry
@Component
struct FloatButtonAnim {
  build() {
    Column() {
      Button("悬浮按钮")
        .width(120)
        .height(48)
        .backgroundColor('#FF6734')
        // 绑定属性动画
        .animateAttr({
          duration: 2000,
          curve: Curve.EaseInOut,
          iterations: -1,
          playMode: PlayMode.Alternate
        })
        // 纵向上下偏移浮动
        .offset({y:20})
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

4.2 旋转动画:加载转圈图标

ets

@Entry
@Component
struct RotateAnimDemo {
  build() {
    Column() {
      Rect()
        .width(80)
        .height(80)
        .fillColor('transparent')
        .stroke({width:6, color:'#007DFF'})
        .borderRadius(40)
        // 无限旋转动画
        .animateAttr({
          duration: 1200,
          curve: Curve.Linear, // 线性匀速旋转
          iterations: -1
        })
        .rotate({angle:360})
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

五、项目实战 1:自定义弹窗入场退场动画(联动前文 CustomDialog)

原生弹窗生硬无动画,这里给之前项目用到的自定义弹窗添加从上弹出、从底部滑入、渐入动画,直接无缝对接之前弹窗代码。

ets

// 带缩放+透明入场动画的自定义弹窗
@CustomDialog
struct AnimDialog {
  controller: CustomDialogController
  @DialogParam confirmClick:()=>void

  build() {
    Column({space:20}) {
      Text("温馨提示").fontSize(20).fontWeight(FontWeight.Bold)
      Text("当前操作不可撤销,是否继续?").fontSize(16)
      Button("确认").width(120).onClick(()=>{
        this.confirmClick()
        this.controller.close()
      })
    }
    .width(300)
    .padding(24)
    .backgroundColor(Color.White)
    .borderRadius(16)
    // 弹窗入场动画:缩放+透明过渡
    .animateAttr({duration:250, curve:Curve.EaseOut})
    .scale({x:0.8, y:0.8})
    .opacity(0)
  }
}

@Entry
@Component
struct DialogAnimPage {
  dialogCtrl:CustomDialogController = new CustomDialogController({
    builder:AnimDialog({confirmClick:()=>console.log("确认点击")}),
    autoCancel:true
  })

  build() {
    Column() {
      Button("打开带动画弹窗").width(220).onClick(()=>{
        this.dialogCtrl.open()
      })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

六、项目实战 2:页面路由自定义转场动画(路由跳转动画)

默认页面跳转生硬,支持左滑入场、底部弹出、渐隐切换三种主流页面转场动画,修改 router 路由配置即可。

ets

import router from '@ohos.router'

@Entry
@Component
struct AnimRouterIndex {
  build() {
    Column({space:25}) {
      Text("首页页面").fontSize(24).fontWeight(FontWeight.Bold)
      // 1. 左侧滑入动画(常规页面跳转)
      Button("左滑跳转详情页").width(220).onClick(()=>{
        router.pushUrl({
          url:"pages/detail/detail",
          transition:RouterTransition.SlideLeft
        })
      })
      // 2. 底部弹出页面动画
      Button("底部弹出页面").width(220).onClick(()=>{
        router.pushUrl({
          url:"pages/detail/detail",
          transition:RouterTransition.SlideBottom
        })
      })
      // 3. 渐隐切换页面
      Button("渐隐切换页面").width(220).onClick(()=>{
        router.pushUrl({
          url:"pages/detail/detail",
          transition:RouterTransition.Fade
        })
      })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

路由全部内置转场动画枚举

  • SlideLeft:页面向左推入(最常用,系统原生标准)
  • SlideBottom:页面从底部向上弹出
  • Fade:全局透明渐入渐出
  • Scale:缩放页面切换

七、综合实战:列表条目侧滑删除动画(电商 / 资讯项目必备)

结合 List 列表 + 显式动画,实现 App 主流的列表项左滑显示删除按钮交互,完全复刻原生 APP 效果。

ets

@Entry
@Component
struct ListSlideAnimDemo {
  @State listOffset: number = 0

  build() {
    List() {
      ListItem() {
        Stack() {
          // 前置展示内容
          Row() {
            Text("订单列表条目").fontSize(16).layoutWeight(1)
          }
          .width('100%')
          .height(70)
          .padding(15)
          .backgroundColor(Color.White)
          .offset({x:this.listOffset})

          // 右侧隐藏删除按钮
          Row() {
            Text("删除").fontColor(Color.White)
          }
          .width(80)
          .height(70)
          .backgroundColor('#f56c6c')
          .position({x:'100%'})
        }
      }
      // 点击触发侧滑动画
      .onClick(()=>{
        animateTo({duration:300, curve:Curve.EaseOut},()=>{
          // 左滑展示删除按钮
          this.listOffset = this.listOffset===0 ? -80 : 0
        })
      })
    }
    .width('100%')
    .padding(15)
    .backgroundColor('#F5F5F5')
  }
}

八、动画开发最佳实践与避坑指南

8.1 动画时长规范(统一项目 UI 风格)

  • 弹窗、按钮点击:200ms-300ms(轻快不拖沓)
  • 页面转场:350ms(系统原生一致)
  • 呼吸、悬浮循环动画:1500ms-2000ms

8.2 曲线选择建议

  • 常规交互:Curve.EaseOut(先快后慢,最贴合手机原生)
  • 匀速旋转加载:Curve.Linear
  • 往返微动呼吸动画:Curve.EaseInOut

8.3 高频避坑点

  1. 动画内部禁止频繁修改大量状态变量,避免页面卡顿
  2. 长列表不要给每一个条目添加高频动画,防止性能雪崩
  3. 页面销毁时,不会自动终止无限循环动画,复杂页面建议在 aboutToDisappear 手动终止动画
  4. 路由转场动画仅支持 pushUrl 跳转,replaceUrl 不推荐搭配动画
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐