首页 > 编程笔记 > 通用技能 阅读:3

ArkUI属性动画详解(附带实例)

属性接口(以下简称属性)包含尺寸属性、布局属性、位置属性等多种类型,用于控制组件的行为。针对当前界面上的组件,其部分属性(如位置属性)的变化会引起 UI 的变化。添加动画可以让属性值从起点逐渐变化到终点,从而产生连续的动画效果。

根据变化时是否能够添加动画,可以将属性分为可动画属性和不可动画属性。判断一种属性是否适合作为可动画属性,主要有以下两个标准:

属性动画分类

1) 可动画属性

对于可动画属性,系统不仅提供通用属性,还支持自定义可动画属性。

① 系统可动画属性:组件自带的支持改变 UI 界面的属性接口,如位置、缩放、模糊等,具体如下表所示。

表:可动画属性及说明
分类 说明
布局属性 位置、大小、内边距、外边距、对齐方式、权重等
仿射变换 平移、旋转、缩放、锚点等
背景 背景颜色、背景模糊等
内容 文字大小、文字颜色,图片对齐方式、模糊等
前景 前景颜色等
Overlay Overlay 属性等
外观 透明度、圆角、边框、阴影等

② 自定义可动画属性:ArkUI 提供 @AnimatableExtend 装饰器来自定义可动画属性。开发者可从自定义绘制的内容中抽象出可动画属性,用于控制每帧绘制的内容,如自定义绘制音量图标。通过自定义可动画属性,可以为 ArkUI 中部分原本不支持动画的属性添加动画。

通常,可动画属性的参数数据类型必须具备连续性,即可以通过插值方法来填充数据点之间的空隙,达到视觉上连续的效果。但是,属性的参数数据类型是否能够进行插值不是决定属性是否可动画的关键因素。

例如,对于设置元素水平方向布局的 direction 属性,其参数数据类型是枚举值;但是,由于位置属性是可动画属性,ArkUI 同样支持在因其属性值改变而引起组件位置变化时添加动效。

2) 不可动画属性

主要是指组件的一些属性不支持动画设置,比如 zIndex、focusable 等属性就是不可动画属性。

实现属性动画

通过可动画属性的改变引起 UI 上产生连续的视觉效果,即为属性动画。属性动画是最基础易懂的动画,ArkUI 提供两种属性动画接口(animateTo和animation)驱动组件属性按照动画曲线等动画参数进行连续的变化,产生属性动画。

属性动画接口详细信息如下表所示:

表:属性动画接口详细信息
属性动画接口 作用域 原理 使用场景
animateTo 闭包内改变属性引起的界面变化。作用于出现、消失转场 通用函数,对闭包前的界面和闭包中的状态变量引起的界面之间的差异做动画。支持多次调用,支持嵌套 适用于对多个可动画属性配置相同动画参数的场景;需要嵌套使用动画的场景
animation 组件通过属性接口绑定的属性变化引起界面变化 识别组件的可动画属性变化,自动添加动画。组件的接口调用是从下往上执行的,animation 只会作用于在其之上的属性调用。组件可以根据调用顺序对多个属性设置不同的 animation 适用于对多个可动画属性配置不同动画参数的场景

1) 使用animateTo产生属性动画

使用 animateTo 产生属性动画的格式如下:
animateTo(value: AnimateParam, event: () => void): void
在 animateTo 接口参数中,value 指定 AnimateParam 对象(包括时长、Curve 等),event 为动画的闭包函数,闭包内因变量改变而产生的属性动画遵循相同的动画参数。

直接使用 animateTo 可能导致实例不明确的问题,推荐先使用 getUIContext 获取 UIContext 实例,再使用实例的 animateTo 进行调用。示例代码如下:
/* 导入动画曲线库 */
import { curves } from '@kit.ArkUI';

/* 入口组件 */
@Entry
@Component
struct Demo1001 {
  /* ========= 第一步:声明相关状态变量 ========= */
  @State animate: boolean = false;          // 控制动画触发
  @State rotateValue: number = 0;           // 组件一的旋转角度
  @State translateX: number = 0;            // 组件二的水平偏移量
  @State opacityValue: number = 1;          // 组件二的透明度

  /* ========= 第二步:在 build() 中将状态变量绑定到可动画属性 ========= */
  build() {
    Row() {
      /* 组件一:旋转动画 */
      Column() {
        // 内容为空,仅做视觉展示
      }
        .rotate({ angle: this.rotateValue })           // 绑定旋转角度
        .backgroundColor('#317AF7')
        .justifyContent(FlexAlign.Center)
        .width(100)
        .height(100)
        .borderRadius(30)
        .onClick(() => {
          /* 第三步:闭包内通过状态变量改变 UI 界面
             系统会检测改变后的 UI 与之前 UI 的差异,并对差异部分添加动画 */
          this.getUIContext()?.animateTo({ curve: curves.springMotion() }, () => {
            this.animate = !this.animate;               // 触发状态翻转

            /* 组件一的 rotate 属性变化 → 旋转动画 */
            this.rotateValue = this.animate ? 90 : 0;

            /* 组件二的 opacity 属性变化 → 透明度动画 */
            this.opacityValue = this.animate ? 0.6 : 1;

            /* 组件二的 translate 属性变化 → 位移动画 */
            this.translateX = this.animate ? 50 : 0;
          });
        });

      /* 组件二:透明度 + 位移动画 */
      Column() {
        // 内容为空,仅做视觉展示
      }
        .justifyContent(FlexAlign.Center)
        .width(100)
        .height(100)
        .backgroundColor('#D94838')
        .borderRadius(30)
        .opacity(this.opacityValue)          // 绑定透明度
        .translate({ x: this.translateX });   // 绑定水平偏移
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}
初始显示效果如下图所示:


图 1 初始显示效果

点击蓝色方块后,蓝色方块顺时针旋转 90°,红色方块横向移动并且逐渐透明,动画停止后的效果如下图所示:


图 2 点击蓝色方块后的效果

再次点击蓝色方块,蓝色方块逆时针旋转 90°,红色方块向左平移的同时透明度也发生变化,动画停止后的效果如下图所示:


图 3 再次点击蓝色方块后的效果

2) 使用animation产生属性动画

相比于 animateTo 接口需要把要执行动画的属性的修改放在闭包中,animation 接口无须使用闭包,只需将 animation 接口写在要做属性动画的可动画属性后即可。animation 只要检测到其绑定的可动画属性发生变化,就会自动添加属性动画。

示例代码如下:
// 导入动画曲线
import { curves } from '@kit.ArkUI';

@Entry
@Component
struct AnimationDemo {
  /* ========= 第一步:声明相关状态变量 ========= */
  @State animate: boolean = false;      // 动画触发开关
  @State rotateValue: number = 0;       // 组件一的旋转角度
  @State translateX: number = 0;        // 组件二的水平偏移量
  @State opacityValue: number = 1;      // 组件二(及父容器)透明度

  /* ========= 第二步:将状态变量设置到相关可动画属性接口 ========= */
  build() {
    Row() {
      /* ------------ 组件一 ------------ */
      Column() {
        // 内容为空,仅作视觉展示
      }
        .opacity(this.opacityValue)                           // 绑定透明度
        .rotate({ angle: this.rotateValue })                  // 绑定旋转角度
        /* 第三步:通过属性动画接口开启属性动画 */
        .animation({ curve: curves.springMotion() })
        .backgroundColor('#317AF7')
        .justifyContent(FlexAlign.Center)
        .width(100)
        .height(100)
        .borderRadius(30)
        .onClick(() => {
          this.animate = !this.animate;

          /* 第四步:闭包内通过状态变量改变 UI 界面
             系统会检测改变后的 UI 与之前 UI 的差异,对有差异的部分添加动画 */

          // 组件一的 rotate 属性发生变化 → 旋转动画
          this.rotateValue = this.animate ? 90 : 0;

          // 组件二的 translate 属性发生变化 → 位移动画
          this.translateX = this.animate ? 50 : 0;

          // 父组件 Column 的 opacity 属性变化 → 自身及子节点透明度动画
          this.opacityValue = this.animate ? 0.6 : 1;
        });

      /* ------------ 组件二 ------------ */
      Column() {
        // 内容为空,仅作视觉展示
      }
        .justifyContent(FlexAlign.Center)
        .width(100)
        .height(100)
        .backgroundColor('#D94838')
        .borderRadius(30)
        .opacity(this.opacityValue)                       // 绑定透明度
        .translate({ x: this.translateX })                // 绑定水平偏移
        .animation({ curve: curves.springMotion() });     // 属性动画
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}
初始显示效果如下图所示:


图 4 初始显示效果

点击蓝色方块后,蓝色方块顺时针旋转 90° 的同时透明度发生变化,红色方块向右平移的同时透明度也发生变化,动画结束后的效果如下图所示。


图 5 第一次点击蓝色方块的效果

再次点击蓝色方块,蓝色方块逆时针旋转 90° 的同时透明度发生变化,红色方块向左平移的同时透明度也发生变化,动画结束后的显示效果如下图所示。


图 6 第二次点击蓝色方块的效果

相关文章