《鸿蒙纪元》 是 张风捷特烈 计划打造的一套 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;
}
}
剩下最关键的在什么时机触发 ResultDisplay
的 startAnimation
方法。从需求来看,每次校验时,无论上一次结果是否和这次相同,都希望触发动画。 触发校验的行为在 GuessingPage
组件中,而动画的事件在 ResultDisplay
组件。现在的问题等价于父组件中如何触发子组件的方法。
鸿蒙开发中,有 @Watch
注解,可以让子组件监听某个属性的变化,当 属性值变化时,可以通过触发回调事件。但这里监听 ResultDisplay
的 result
有个问题,那就是如果这次和上次的校验结果一致,就不会触发通知。于是这里增加了一个 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 脑图电子版,让我们一起成长,变得更强。我们下次再见~