鸿蒙纪·梦始卷#06 | 猜数字 - 动画的使用

436 阅读5分钟

《鸿蒙纪元》张风捷特烈 计划打造的一套 HarmonyOS 开发系列教程合集。致力于创作优质的鸿蒙原生学习资源,帮助开发者进入纯血鸿蒙的开发之中。本系列的所有代码将开源在 HarmonyUnit 项目中:

github: github.com/toly1994328…
gitee: gitee.com/toly1994328…

鸿蒙纪元 系列文章列表可在《文章总集》 或 【github 项目首页】 查看。


本文是《鸿蒙纪·梦始卷》 的第六章,上一篇我们实现了 猜数字 的交互功能和状态数据处理。从中认识了 @Prop 注解和 @BuilderParam 自定义插槽组件的 this 指向问题:

本文将介绍一下鸿蒙开发中 动画 的使用,并完成猜数字的最后一步:校验结果区域的动画反馈效果。


1. 什么是动画

动画本质上就是不断更新界面展示的内容,玩过翻页动画的感触会深一些:

在每页纸上绘制连续的动作,手快速翻动时,内容快速变化,形成连续运动的动画效果。

这里根据翻页动画,先统一给定几个概念描述,方便后续的表述:

  • 动画帧 : 一页纸上的内容。
  • 动画时长 : 从开始翻看,到结束的时间差。
  • 帧率 : 动画过程中,每秒钟包含动画帧的个数。
  • 动画控制动器: 动画进行的动力来源,比如翻页动画中的手。

其实对于界面来说也是类似的,屏幕上展示的内容不断变化,给人视觉上的动画效果。对于界面编程来说,动画一般都是改变某些属性值。下面是圆形的缩放效果,有动画会让用户感觉非常流畅,而无动画会让变化生硬:

有动画无动画

2. 形状与缩放

在学习一项技术过程中会写一些小的案例,我喜欢建一个 playground 的文件夹,在其中写一写简单独立的小功能。比如这个小球缩放的动画,通过 CircleScaleAnimation 组件来展示:

在构建逻辑中,可以通过 Shape 组件展示基本形状,Circle 组件可以展示圆形。圆形在构造时指定宽高;通过 fill 来指定填充色。代码如下所示:

@Component
export struct CircleScaleAnimation {

  build() {
    Shape() {
      Circle({ width: 80, height: 80, }).fill('#317bd4')
    }
  }
}

缩放操作 .scale 是所有组件的通用方法。下面代码中 scaleValue 是状态量,表示缩放的比例;点击按钮时触发 run 方法,调用 onEvent 来更新 scaleValue 状态值。这样就可以实现无动画版的缩放切换:

import { curves } from "@kit.ArkUI";

@Component
export struct CircleScaleAnimation {
  @State scaleValue: number = 1;

  build() {
    Shape() {
      Circle({ width: 80, height: 80, }).fill('#317bd4')
    }
    .scale({ x: this.scaleValue, y: this.scaleValue })
    .onClick(() => this.run())
  }

  run(): void {
    this.onEvent();
  }

  onEvent(): void {
    this.scaleValue = this.scaleValue == 1 ? 0.5 : 1;
  }
}

3. 属性动画的使用

如下所示,可以通过 this.getUIContext()?.animateTo 方法,使属性的变化具有动画效果。其中

  • 第一参数是动画的配置参数 AnimateParam ,比如 时长(ms)、 动画曲线、动画停止回调等。
  • 第二参是回调函数,用于处理属性值的变换,也就是上面的 onEvent 方法。
run(): void {
  let param: AnimateParam = { curve: curves.springMotion(), duration: 1000 };
  this.getUIContext()?.animateTo(param, () => this.onEvent())
}

只需要两代码的处理,就可以让一个属性值的变化具有动画效果,是不是非常简单:


4. 完成猜数字功能需求

如下是猜数字的完整流程,在生成数字之后,用户在输入框中的输入后,点击运行时。无论是大了还是小了,文字都会有一个字体放大的动画效果给出反馈:

--

我们之前将提示信息的界面封装成 ResultDisplay 组件进行展示,现在想要修改面板的展示效果,只要对该组件进行优化即可。可以很快定位到 ResultDisplay.ets, 这也是各司其职的一个好处。下面就来看一下,让文字进行动画变化的流程。


这里的目的是让展示的文字进行动画,所以属性动画针对的是 文字大小;如下代码中定义 fontSize 状态数据,在 startAnimation 中让属性值变化到 42 :

@Component
export struct ResultDisplay {
  @State fontSize: number = 14;
  
  /// build 略同...
  
  startAnimation(): void {
    let param: AnimateParam = { curve: Curve.EaseIn, duration: 500 };
    this.getUIContext()?.animateTo(param, () => this.onEvent());
  }

  onEvent(): void {
    this.fontSize = 42;
  }
}

剩下最关键的在什么时机触发 ResultDisplaystartAnimation 方法。从需求来看,每次校验时,无论上一次结果是否和这次相同,都希望触发动画。 触发校验的行为在 GuessingPage 组件中,而动画的事件在 ResultDisplay 组件。现在的问题等价于父组件中如何触发子组件的方法。

鸿蒙开发中,有 @Watch 注解,可以让子组件监听某个属性的变化,当 属性值变化时,可以通过触发回调事件。但这里监听 ResultDisplayresult 有个问题,那就是如果这次和上次的校验结果一致,就不会触发通知。于是这里增加了一个 guessCount 的属性,记录猜测的次数;并通过 @Watch 修饰,这样每次次数变化时,就触发 onGuessCountChange 方法,在其中触发 startAnimation 即可:

@Component
export struct ResultDisplay {
  @Prop result: CheckResult = CheckResult.none;
  @Prop @Watch('onGuessCountChange') guessCount: number = 0;
  
  onGuessCountChange(propName: string): void {
    this.fontSize = 14;
    this.startAnimation();
  }
  

GuessingState 状态类中需要额外维护一个 guessCount 状态数据,每次检查时增加 1 。最后在 ResultDisplay 中间构建时传入 guessCount 即可:

export class GuessingState {
  guessCount: number = 0;
  /// 略同...

  checkResult(): void {
    /// 略同...
    this.guessCount++;
  }

尾声

到这里,猜数字的所有功能就已经实现了,提交一个小里程碑:v9-猜数字-结果动画反馈。后续将会继续带来其他有趣的小功能来体验鸿蒙开发。
更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。关注 公众号 并回复 鸿蒙纪元 可领取最新的 xmind 脑图电子版,让我们一起成长,变得更强。我们下次再见~