HarmonyOS6 - 鸿蒙ArkUI动画详解

HarmonyOS6 - 鸿蒙ArkUI动画详解

开发环境为:

开发工具:DevEco Studio 6.0.1 Release

API版本是:API21

本文所有代码都已使用模拟器测试成功!

1. ArkUI动画简介

1. 使用动画原因

在ArkUI中,组件属性的变化,通常会引起UI的变化。属性会在一瞬间完成变化。往往造成突兀感,同时容易导致用户失去视觉焦点。

增加动画可在UI发生变化时,增加流畅的过渡效果,从而增强用户体验。

动画的目的包括:

  • 使界面的过渡自然流畅。
  • 增强用户从界面获得的反馈感和互动感。
  • 在内容加载等场景中,增加用户的耐心,缓解等待带来的不适感。
  • 引导用户了解和操作设备。

在需要为UI变化添加过渡的场景,都可以使用动画,如开机、应用启动退出、下拉进入控制中心等。这些动画可向用户提供关于其操作的反馈,并有助于让用户始终关注界面。

2. 动画的原理

ArkUI中提供多种动画接口(属性动画、转场动画等),用于驱动属性值按照设定的动画参数,从起始值逐渐变化到终点值。尽管变化过程中参数值并非绝对的连续,而是具有一定的离散性。但由于人眼会产生视觉暂留,所以最终看到的就是一个"连续"的动画。

  • UI的一次改变称为一个动画帧,对应一次屏幕刷新。决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second),即每秒的动画帧数,帧率越高则动画就会越流畅。
  • ArkUI中,动画参数包含了如动画时长、动画曲线等参数。
  • 动画曲线作为主要因素,决定了属性值变化的规律。以线性动画曲线为例,在动画时长内,属性值将从起点值匀速变化到终点值。属性过快或过慢的变化,都可能带来不好的视觉感受,影响用户体验。因此动画参数特别是动画曲线,需要结合场景和曲线特点进行设计和调整。

3. 动画的分类

  • 属性动画:最基础的动画类型,按照动画参数逐帧驱动属性的变化,产生一帧帧的动画效果。除其中的自定义属性动画外,动画过程的驱动由系统完成,应用侧不感知动画过程。
  • 转场动画:为组件在出现和消失时添加过渡动画。为了保证动画一致性,部分接口动画曲线已内置,不支持开发者自定义。
    • 不建议在应用内使用UIAbility组合所有的界面:UIAbility是一个任务,会在多任务界面独立显示为一个卡片,UIAbility之间的跳转相当于任务之间的跳转。以应用内查看大图场景为例,不建议调用图库的UIAbility来打开图片查看大图,因为这会导致任务的跳转,图库的UIAbility也会加入多任务界面中。正确的方式是应用内构建大图组件,通过模态转场去调起大图组件,一个任务内的所有的界面都在一个UIAbility内闭环。
    • 导航转场中,应使用Navigation组件实现转场动画。过去的page+router方式在实现导航转场过程中,因为page和page之间相互独立,其联动动画效果受限。不仅容易导致页面之间的割裂,并且不支持一次开发多端部署。
  • 粒子动画:介绍粒子动画的原理及使用方法。
  • 组件动画:组件提供默认动效(如List的滑动动效)便于开发者使用,同时部分组件还支持定制化动效。
  • 动画曲线:介绍传统曲线和弹簧曲线的特点和使用方式。动画曲线影响属性值的运动规律,进而决定界面的动画效果。
  • 动画衔接:介绍如何实现动画与动画之间、手势与动画之间的自然过渡。
  • 动画效果:介绍模糊、大阴影和颜色渐变等高阶效果接口的使用方法。
  • 帧动画:系统侧提供在动画过程中的插值结果,由开发者每帧修改属性值产生动画,相比于属性动画,有可实现暂停的优点,但性能不如属性动画。

2. 属性动画

1. 属性动画简介

通过动画属性改变引起UI上产生的连续视觉效果,即为属性动画 。根据变化时是否能够添加动画,可以将属性分为可动画属性和不可动画属性。

  • 不可动画属性:比如用于控制组件是否可以响应点击、触摸等事件的属性enabled属性。决定当前组件是否可以获得焦点的属性focusable
  • 可动画属性:布局属性中的位置、大小、内边距、外边距、对齐方式、权重。仿射变换中的平移、旋转、缩放、锚点等。背景中的背景颜色、背景模糊。内容中的字大小、文字颜色,图片对齐方式、模糊等。外观中的透明度、圆角、边框、阴影等。

2. 接口说明

ArkUI提供两种属性动画接口animateTo和animation驱动组件属性按照动画曲线等动画参数进行连续的变化,产生属性动画。它们的区别是:

  • animateTo:适用对多个可动画属性配置相同动画参数的动画。
js 复制代码
animateTo(value: AnimateParam, event: () => void): void

说明:animateTo接口参数中,value指定动画参数(包括时长、曲线等)event为动画的闭包函数,闭包内变量改变产生的属性动画将遵循相同的动画参数。

  • animation :适用于对多个可动画属性配置不同参数动画的场景。

3. animateTo接口案例演示

实现如下效果:

思路分析:

第1步:构建3个状态变量,分别用来保存旋转的角度、y坐标的偏移量以及透明度。

第2步:在设置属性时,对对应的Text组件设置参数。

第3步:在按钮的点击事件中,调用animateTo()接口。对对应的状态变量进行修改。

案例代码如下:

js 复制代码
import curves from '@ohos.curves'

@Entry
@Component
struct AnimateToDemo {
  @State animate: boolean = false;
  // 第一步: 声明相关状态变量
  @State rotateValue: number = 0; // 组件一旋转角度
  @State translateY: number = 0; // 组件二偏移量
  @State opacityValue: number = 1; // 组件二透明度

  // 第二步:将状态变量设置到相关可动画属性接口
  build() {
    Column() {
      // 组件一
      Column() {
        Text('ArkUI')
          .fontWeight(FontWeight.Bold)
          .fontSize(20)
          .fontColor(Color.White)
      }
      .justifyContent(FlexAlign.Center)
      .width(150)
      .height(150)
      .borderRadius(10)
      .rotate({ angle: this.rotateValue })
      .backgroundColor(0xf56c6c)

      // 组件二
      Column() {
        Text('ArkUI')
          .fontWeight(FontWeight.Bold)
          .fontSize(20)
          .fontColor(Color.White)
      }
      .justifyContent(FlexAlign.Center)
      .width(150)
      .height(150)
      .backgroundColor(0x67C23A)
      .borderRadius(10)
      .opacity(this.opacityValue)
      .translate({ y: this.translateY })

      Button('Click')
        .margin({ top: 120 })
        .onClick(() => {
          this.animate = !this.animate;
          // 第三步:通过属性动画接口开启属性动画
          animateTo({ curve: curves.springMotion() }, () => {
            // 第四步:闭包内通过状态变量改变UI界面
            // 这里可以写任何能改变UI的逻辑比如数组添加,显隐控制,系统会检测改变后的UI界面与之前的UI界面的差异,对有差异的部分添加动画
            // 组件一的rotate属性发生变化,所以会给组件一添加rotate旋转动画
            this.rotateValue = this.animate ? 90 : 0;
            // 组件二的scale属性发生变化,所以会给组件二添加scale缩放动画
            this.opacityValue = this.animate ? 0.6 : 1;
            // 组件二的offset属性发生变化,所以会给组件二添加offset偏移动画
            this.translateY = this.animate ? 100 : 0;
          })
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

4. animation接口案例演示

效果如下:

思路分析:

第1步:构建4个状态变量,分别用来保存旋转的角度、y坐标的偏移量、字体颜色及透明度。

第2步:将状态变量设置到相关可动画属性接口。

第3步:对每个需要添加动画的组件,通过属性动画接口animation开启属性动画。需要指出的是,接口必须紧挨着设置动画属性方法后面。

第4步:在按钮的点击事件中,对对应的状态变量进行修改。

代码如下:

js 复制代码
import curves from '@ohos.curves';

@Entry
@Component
struct AnimateToDemo {
  @State animate: boolean = false;
  // 第一步: 声明相关状态变量
  @State rotateValue: number = 0; // 组件一旋转角度
  @State translateY: number = 0; // 组件二偏移量
  @State color: Color = Color.White; // 组件二字体颜色
  @State opacityValue: number = 1; // 父组件透明度

  // 第二步:将状态变量设置到相关可动画属性接口
  build() {
    Column() {
      Column() {
        // 组件一
        Text('ArkUI')
          .fontWeight(FontWeight.Bold)
          .fontSize(20)
          .fontColor(Color.White)
          .textAlign(TextAlign.Center)
          .borderRadius(10)
          .backgroundColor(0xf56c6c)
          .rotate({ angle: this.rotateValue })
          // 第三步:通过属性动画接口开启属性动画,控件的函数调用顺序是从下往上的,这个animation会作用到上面的rotate属性
          .animation(
            {
              curve: curves.springMotion(0.3, 1.0) //创建指定response和dampingFraction、其余参数默认的弹性动画曲线
            })
          .width(150)
          .height(150)

        // 组件二
        Text('ArkUI')
          .fontWeight(FontWeight.Bold)
          .fontSize(20)
          .fontColor(this.color)
          .textAlign(TextAlign.Center)
          .borderRadius(10)
          .backgroundColor(0x67C23A)
          .width(150)
          .height(150)
          .translate({ y: this.translateY })
          // 第三步:通过属性动画接口开启属性动画,控件的函数调用顺序是从下往上的,这个animation会作用到上面的translate属性
          .animation({ curve: curves.springMotion(0.3, 0.6) })//创建指定response和dampingFraction、其余参数默认的弹性动画曲线
      }
      .justifyContent(FlexAlign.Center)
      .opacity(this.opacityValue)
      // 这个animation会作用到上面的opacity属性,父组件column的透明度变化,会导致其子节点的透明度也变化,所以这里会给column和其子节点的透明度属性都加动画
      .animation({ curve: curves.springMotion() })

      // 第四步:通过状态变量改变UI界面,系统会检测改变后的UI界面与之前的UI界面的差异,对有差异的部分添加动画
      Button('Click')
        .margin({ top: 120 })
        .onClick(() => {
          this.animate = !this.animate;
          // 组件一的rotate属性有变化,所以会给组件一加rotate动画
          this.rotateValue = this.animate ? 90 : 0;
          // 组件二的translate属性有变化,所以会给组件二加translate动画
          this.translateY = this.animate ? 100 : 0;
          // 组件二的fontColor属性有变化,所以会给组件二加fontColor动画
          this.color = this.animate ? Color.Black : Color.White;
          // 父组件column的opacity属性有变化,会导致其子节点的透明度也变化,所以这里会给column和其子节点的透明度属性都加动画
          this.opacityValue = this.animate ? 0.6 : 1;
        })

    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

3. 转场动画

1. 转场动画概述

转场动画是指对将要出现或消失的组件做动画,对始终出现的组件做动画应使用属性动画。如果技术选择上使用属性动画,会面临编码麻烦。

2. 转场动画分类

转场动画有如下几类:

  • 出现/消失转场:对新增、消失的控件实现动画效果,是通用的基础转场效果。
  • 模态转场:新的界面覆盖在旧的界面之上的动画,旧的界面不消失,新的界面出现,如弹框就是典型的模态转场动画。
  • 共享元素转场 (一镜到底):共享元素转场是一种界面切换时对相同或者相似的元素做的一种位置和大小匹配的过渡动画效果。
  • 旋转屏动画:旋转屏动画主要分为布局切换的旋转屏动画透明度变化的旋转屏动画,旨在实现屏幕显示方向变化时的自然过渡。
  • 页面转场动画:页面的路由转场方式,可以通过在pageTransition函数中自定义页面入场和页面退场的转场动效。为了实现更好的转场效果,推荐使用导航转场模态转场
  • 导航转场:页面的路由转场方式,对应一个界面消失,另外一个界面出现的动画效果,如设置应用一级菜单切换到二级界面。

3. 出现/消失接口说明

组件的插入、删除过程即为组件本身的转场过程,组件的插入、删除动画称为组件内转场动画。通过组件内转场动画,可定义组件出现、消失的效果。

组件内转场动画的接口为:

js 复制代码
transition(value: TransitionOptions)

transition函数的入参为组件内转场的效果,可以定义平移、透明度、旋转、缩放这几种转场样式的单个或者组合的转场效果,必须和animateTo一起使用才能产生组件转场效果。

另外type用于指定当前的transition动效生效在组件的变化场景,类型为TransitionType

(1)组件的插入、删除使用同一个动画效果

js 复制代码
Button()  .transition({ type: TransitionType.Delete, translate: { x: 200, y: -200 } })

当type属性为TransitionType.All时,表示指定转场动效生效在组件的所有变化(插入和删除)场景。此时,删除动画和插入动画是相反的过程,删除动画是插入动画的逆播。

(2)组件的插入、删除使用不同的动画效果

js 复制代码
Button()  .transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 }, opacity: 0 })  .transition({ type: TransitionType.Delete, rotate: { x: 0, y: 0, z: 1, angle: 360 } })

当组件的插入和删除需要实现不同的转场动画效果时,可以调用两次transition函数,分别设置type属性为TransitionType.Insert和TransitionType.Delete。例如,以上代码定义了一个Button控件。在插入时,组件从相对于组件正常布局位置x方向平移200vp、y方向平移-200vp的位置、透明度为0的初始状态,变化到x、y方向平移量为0、透明度为1的默认状态,插入动画为平移动画和透明度动画的组合。在删除时,组件从旋转角为0的默认状态,变化到绕z轴旋转360度的终止状态,即绕z轴旋转一周。

案例代码:

js 复制代码
@Entry
@Component
struct Page012 {
  @State isPresent: boolean = true;

  build() {
    Column({ space: 15 }) {
      Button('测试')
        .onClick(() => {
          animateTo({ duration: 1000 }, () => {
            // 动画闭包内控制Image组件的出现和消失
            this.isPresent = !this.isPresent;
          })
        })

      if (this.isPresent) {
        Column() {
          Text('ArkUI')
            .fontWeight(FontWeight.Bold)
            .fontSize(20)
            .fontColor(Color.White)
        }
        .justifyContent(FlexAlign.Center)
        .width(150)
        .height(150)
        .borderRadius(10)
        .backgroundColor(0xf56c6c)
        // 第一步:将转场效果通过transition接口设置到组件
        .transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 } })
        .transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0, y: 0 } })
      }
    }
    .height('100%')
    .width('100%')
    .padding({ top: 100 })
    .backgroundColor('#ccc')
  }
}

效果:

4. 动画效果

动画效果可以丰富界面的细节,提升UI界面的真实感和品质感。例如,模糊和阴影效果可以让物体看起来更加立体,使得动画更加生动。ArkUI提供了丰富的效果接口,开发者可快速打造出精致、个性化的效果。本章中主要对常用的模糊、阴影和色彩效果等效果接口进行了介绍。

1. 模糊

1. 概述

模糊可以用来体现界面空间的纵深感,区分前后元素的层级关系

接口 说明
backdropBlur 为当前组件添加背景模糊效果,入参为模糊半径。
blur 为当前组件添加内容模糊效果,入参为模糊半径。
backgroundBlurStyle 为当前组件添加背景模糊效果,入参为模糊样式。
foregroundBlurStyle 为当前组件添加内容模糊效果,入参为模糊样式。
motionBlur 为当前组件添加由缩放大小或位移变化引起的运动过程中的动态模糊效果,入参为模糊半径和锚点坐标。

说明:以上接口是实时模糊接口,会每帧进行实时渲染,性能负载较高。当模糊内容和模糊半径都不需要变化时,建议使用静态模糊接口blur。

2. 案例

需求说明:

使用blur为组件添加内容模糊。这是一个图片模糊度交互演示应用,用户通过在屏幕上下滑动手指来实时调节背景图片的模糊程度。当手指在屏幕上移动时,根据手指的垂直位置计算模糊值;手指离开屏幕后,图片恢复清晰状态。应用实时显示当前手指位置和模糊度数值,为用户提供直观的视觉反馈。

思路分析:

  1. 创建父组件 Blur 并添加状态变量
  2. 实现aboutToAppear 生命周期函数,初始化显示文本
  3. 创建页面布局结构
  4. 实现触摸事件处理

效果:

代码如下:

js 复制代码
@Entry
@Component
struct BlurTest {
  @State radius: number = 0; // 背景图片的模糊值
  @State y: string = '手指不在屏幕上';

  build() {
    Stack({ alignContent: Alignment.Center }) {
      // 背景图片
      Image($r("app.media.bg"))
        .blur(this.radius)
        .width('100%')
        .height('100%')
        .objectFit(ImageFit.Cover)
        .position({ x: 0, y: 0 })

      // 内容区域
      Column({ space: 16 }) {
        // 标题
        Text("图片模糊效果演示")
          .fontSize(32)
          .fontWeight(FontWeight.Bold)
          .margin({ top: 50 })
          .fontColor('#333')

        Column({ space: 10 }) {
          Text('手指所在Y轴位置:' + this.y)
          Text('背景图片模糊程度为:' + this.radius)
        }
        .width('95%')
        .backgroundColor('#ccc')
        .borderRadius(8)
        .padding(10)
        .alignItems(HorizontalAlign.Start)

        // 提示信息
        Text("👆 上下滑动屏幕,可调整背景图模糊程度")
          .fontSize(16)
          .fontColor('#333')
          .opacity(0.8)
      }
      .width('100%')
      .height('100%')
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
    .height('100%')
    .layoutWeight(1)
    .onTouch((event?: TouchEvent) => {
      if (event) {
        if (event.type === TouchType.Move) {
          this.y = Number(event.touches[0].y.toString()).toString();
          this.radius = Number(this.y) / 10;
        }
        if (event.type === TouchType.Up) {
          this.radius = 0;
          this.y = '手指不在屏幕上';
        }
      }
    })
  }
}

2. 阴影

1. 概述

阴影接口shadow可以为当前组件添加阴影效果,该接口支持两种类型参数,开发者可配置ShadowOptions自定义阴影效果。ShadowOptions模式下,当radius = 0 或者 color 的透明度为0时,无阴影效果。

2. 案例

编码思路:

  1. 创建父组件 ShadowOptionDemo
  2. 创建基础布局结构
  3. 实现第一个基础阴影效果卡片
  4. 实现第二个带偏移的阴影效果卡片

效果图如下:

代码如下:

js 复制代码
@Entry
@Component
struct ShadowOptionDemo {
  build() {
    Row() {
      Column() {
        // 第一个演示卡片 - 基础阴影效果
        Column() {
          Text('shadowOption').fontSize(12)
        }
        .width(100)
        .aspectRatio(1)
        .margin(10)
        .justifyContent(FlexAlign.Center)
        .backgroundColor(Color.White)
        .borderRadius(20)
        .shadow({ radius: 20, color: Color.Gray }) // 基础阴影效果

        // 第二个演示卡片 - 带偏移的阴影效果
        Column() {
          Text('shadowOption').fontSize(12)
        }
        .width(100)
        .aspectRatio(1)
        .margin(10)
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#a8a888')
        .borderRadius(20)
        .shadow({
          radius: 20,
          color: Color.Gray,
          offsetX: 20,
          offsetY: 20
        }) // 带偏移的阴影效果
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .height('100%')
    .backgroundColor('#fff')
  }
}

3. 色彩

1. 概述

通过颜色渐变接口,可以设置组件的背景颜色渐变效果,实现在两个或多个指定的颜色之间进行平稳的过渡。

*接口* *说明*
[linearGradient](#接口 说明 linearGradient 为当前组件添加线性渐变的颜色渐变效果。 sweepGradient 为当前组件添加角度渐变的颜色渐变效果。 radialGradient 为当前组件添加径向渐变的颜色渐变效果。) 为当前组件添加线性渐变的颜色渐变效果。
[sweepGradient](#接口 说明 linearGradient 为当前组件添加线性渐变的颜色渐变效果。 sweepGradient 为当前组件添加角度渐变的颜色渐变效果。 radialGradient 为当前组件添加径向渐变的颜色渐变效果。) 为当前组件添加角度渐变的颜色渐变效果。
[radialGradient](#接口 说明 linearGradient 为当前组件添加线性渐变的颜色渐变效果。 sweepGradient 为当前组件添加角度渐变的颜色渐变效果。 radialGradient 为当前组件添加径向渐变的颜色渐变效果。) 为当前组件添加径向渐变的颜色渐变效果。

2. 案例

效果:

代码:

js 复制代码
@Entry
@Component
struct LinearGradientDemo {

  build() {
    Grid() {
      // 这里将放置四个渐变效果卡片
      //第一个默认角度渐变卡片
      GridItem() {
        Column() {
          Text('angle: 180').fontSize(15)
        }
        .width(100)
        .height(100)
        .justifyContent(FlexAlign.Center)
        .borderRadius(10)
        .linearGradient({
          colors: [
            ['#ff3a97f5', 0.0],
            ['#ffffb600', 1.0],
          ]
        })
      }

      //第二个45度角渐变卡片
      GridItem() {
        Column() {
          Text('angle: 45').fontSize(15)
        }
        .width(100)
        .height(100)
        .justifyContent(FlexAlign.Center)
        .borderRadius(10)
        .linearGradient({
          angle: 45,
          colors: [
            ['#ff3a97f5', 0.0],
            ['#ffffb600', 1.0],
          ]
        })
      }

      //第三个重复渐变卡片
      GridItem() {
        Column() {
          Text('repeat: true').fontSize(15)
        }
        .width(100)
        .height(100)
        .justifyContent(FlexAlign.Center)
        .borderRadius(10)
        .linearGradient({
          repeating: true,
          colors: [
            ['#ff3a97f5', 0.0],
            ['#ffffb600', 0.3],
          ]
        })
      }

      //第四个非重复渐变卡片
      GridItem() {
        Column() {
          Text('repeat: false').fontSize(15)
        }
        .width(100)
        .height(100)
        .justifyContent(FlexAlign.Center)
        .borderRadius(10)
        .linearGradient({
          colors: [
            ['#ff3a97f5', 0.0],
            ['#ffffb600', 0.3],
          ]
        })
      }
    }
    .columnsGap(10)
    .rowsGap(10)
    .columnsTemplate('1fr 1fr')
    .rowsTemplate('1fr 1fr 1fr')
    .width('100%')
    .height('100%')
  }
}

5. 帧动画

1. 概述

通过返回应用onFrame逐帧回调的方式,让开发者在应用侧的每一帧都可以设置属性值,从而实现设置了该属性值对应组件的动画效果。

相比于属性动画,开发者可感知动画的过程,实时修改UI侧的值,具有事件可实时响应、可暂停的优点,但性能上不如属性动画。

在属性动画符合要求时更推荐使用属性动画的接口实现,可参照实现属性动画。

2. 使用animator实现动画效果

效果:

代码:

js 复制代码
import { AnimatorResult } from '@kit.ArkUI';

@Entry
@Component
struct AnimatorTest {
  private TAG: string = '[AnimatorTest]'
  private backAnimator: AnimatorResult | undefined = undefined
  @State columnWidth: number = 100
  @State columnHeight: number = 100

  create() {
    this.backAnimator = this.getUIContext().createAnimator({
      // 建议使用 this.getUIContext().createAnimator()接口
      duration: 2000,
      easing: "ease",
      delay: 0,
      fill: "forwards",
      direction: "normal",
      iterations: 1,
      begin: 100, //动画插值起点
      end: 200 //动画插值终点
    })
    this.backAnimator.onFinish = () => {
      console.info(this.TAG, 'backAnimator onFinish')
    }
    this.backAnimator.onRepeat = () => {
      console.info(this.TAG, 'backAnimator repeat')
    }
    this.backAnimator.onCancel = () => {
      console.info(this.TAG, 'backAnimator cancel')
    }
    this.backAnimator.onFrame = (value: number) => {
      this.columnWidth = value
      this.columnHeight = value
    }
    console.info(this.TAG, 'create finish')
  }

  aboutToDisappear() {
    // 自定义组件消失时调用finish使未完成的动画结束,避免动画继续运行。
    // 由于backAnimator在onframe中引用了this, this中保存了backAnimator,
    // 在自定义组件消失时应该将保存在组件中的backAnimator置空,避免内存泄漏
    this.backAnimator?.finish();
    this.backAnimator = undefined;
  }

  build() {
    Column() {
      Column() {
        Column()
          .width(this.columnWidth)
          .height(this.columnHeight)
          .backgroundColor(Color.Red)
      }
      .width('100%')
      .height(300)

      Column() {
        Row() {
          Button('create')
            .fontSize(30)
            .fontColor(Color.Black)
            .onClick(() => {
              this.create()
            })
        }
        .padding(10)

        Row() {
          Button('play')
            .fontSize(30)
            .fontColor(Color.Black)
            .onClick(() => {
              if (this.backAnimator) {
                this.backAnimator.play()
              }
            })
        }
        .padding(10)
      }
    }
  }
}

3. 使用animator实现小球抛物运动

效果如下:

代码如下:

js 复制代码
import { AnimatorOptions, AnimatorResult } from '@kit.ArkUI';

@Entry
@Component
struct Page014 {
  @State animatorOptions: AnimatorResult | undefined = undefined;
  @State animatorStatus: string = '创建';
  @State translateX: number = 0;
  @State translateY: number = 0;
  // 动画相关配置参数
  begin: number = 0;
  end: number = 300;
  topWidth: number = 150;
  bottomHeight: number = 100;
  g: number = 0.18;
  // 动画配置选项
  animatorOption: AnimatorOptions = {
    duration: 4000,
    delay: 0,
    easing: 'linear',
    iterations: 1,
    fill: "forwards",
    direction: 'normal',
    begin: this.begin,
    end: this.end
  };

  onPageShow(): void {
    this.animatorOptions = this.getUIContext().createAnimator(this.animatorOption)

    // 动画帧回调
    this.animatorOptions.onFrame = (progress: number) => {
      this.translateX = progress;
      if (progress > this.topWidth && this.translateY < this.bottomHeight) {
        this.translateY = Math.pow(progress - this.topWidth, 2) * this.g;
      }
    }

    // 各种状态回调
    this.animatorOptions.onCancel = () => {
      this.animatorStatus = '取消';
    }
    this.animatorOptions.onFinish = () => {
      this.animatorStatus = '完成';
    }
    this.animatorOptions.onRepeat = () => {
      console.log("动画重复播放");
    }
  }

  onPageHide(): void {
    this.animatorOptions = undefined;
  }

  build() {
    Column() {
      Column({ space: 30 }) {
        Button('播放').onClick(() => {
          this.animatorOptions?.play();
          this.animatorStatus = '播放中';
        }).width(80).height(35)

        Button("重置").onClick(() => {
          this.translateX = 0;
          this.translateY = 0;
        }).width(80).height(35)

        Button("暂停").onClick(() => {
          this.animatorOptions?.pause();
          this.animatorStatus = '暂停';
        }).width(80).height(35)
      }.width("100%").height('25%')


      Stack() {
        Button()
          .width(60)
          .height(60)
          .translate({ x: this.translateX, y: this.translateY })
      }
      .width("100%")
      .height('45%')
      .align(Alignment.Start)

      Text("当前动画状态为:" + this.animatorStatus)
    }
  }
}

最后

  • 希望本文对你有所帮助!
  • 本人如有任何错误或不当之处,请留言指出,谢谢!
相关推荐
HMS Core2 小时前
【FAQ】HarmonyOS SDK 闭源开放能力 — Media Kit
华为·harmonyos
IT陈图图3 小时前
Flutter × OpenHarmony 音乐播放器应用 - 构建录音控制区域
flutter·华为·鸿蒙·openharmony
Mr. Sun_4 小时前
华为云杉系统交换机普通线缆堆叠
华为·堆叠·云杉
不会写代码0005 小时前
Flutter 框架跨平台鸿蒙开发 - 手写笔记工具开发教程
笔记·flutter·华为·harmonyos
无名的小三轮6 小时前
华为eNSP中USG6000防火墙web界面登录设置
网络·笔记·安全·web安全·华为
小风呼呼吹儿6 小时前
Flutter 框架跨平台鸿蒙开发 - 老年便捷助手:打造适合老年人的智能工具箱
flutter·华为·harmonyos
lbb 小魔仙6 小时前
【Harmonyos】开源鸿蒙跨平台训练营DAY3:为开源鸿蒙跨平台工程集成网络请求能力,实现数据清单列表的完整构建与开源鸿蒙设备运行验证(及常见问题与方法)
windows·开源·harmonyos·鸿蒙·开源鸿蒙·鸿蒙开平台应用
奔跑的露西ly6 小时前
【HarmonyOS NEXT】Stage模型与FA模型对比
华为·harmonyos·鸿蒙
小雨下雨的雨6 小时前
禅息:在鸿蒙与 Flutter 之间寻找呼吸的艺术
算法·flutter·华为·重构·交互·harmonyos