简介
HarmonyOS 提供了AnimatorValue来执行属性动画,但是其方法数很少,并且属性值范围局限于[0,1],也不直接支持动画反转等一些常用的操作。在日常开发中,我们经常需要基于这个类进行扩展编写,去适应真实场景。因此,通过收集常用的场景,整理出一个属性动画的扩展类ValueAnimator。
效果演示
实现思路
1. 动画分类
实际开发过程,我们大部分的动画都是作用于视图组件。任何复杂的动画,通过逐帧分解,最终都可以归纳为如下几种基础动画的组合:
- X,Y轴缩放动画
- X,Y轴平移动画
- 透明度动画
- 旋转角度动画
- 组件宽高尺寸动画
2. 动画操作
对于动画的操作,我们可以归纳出以下这些动作:
- 开始动画
- 暂停动画
- 取消动画
- 结束动画
- 反转动画
- 设置动画起始值
- 设置动画延时
- 设置动画执行时长
- 设置动画插值器
- 设置动画状态监听
- 设置动画进度监听
- 设置动画执行次数
- 设置动画重复模式
- 设置组件动画属性值
3. 代码实现
3.1 视图动画的实现
系统原生提供的AnimatorValue为我们提供了[0,1]的动画范围。因此最简单的实现方式是定义一个ValueAnimator,内部包含一个系统的AnimatorValue来计算[0,1]的进度值,通过自己的逻辑转换为我们期望的动画值。
- public class ValueAnimator {
- private AnimatorValue innerAnimator;
- // 监听动画值的变化
- private final AnimatorValue.ValueUpdateListener valueUpdateListener = new AnimatorValue.ValueUpdateListener() {
- @Override
- public void onUpdate(AnimatorValue animatorValue, float fraction) {
- Object[] takeValues = values;
- // 动画反转运算处理
- if (takeReverseLogic && isReversing) {
- takeValues = reverseValues;
- }
- Object animatedValue = takeValues[0];
- // fraction为[0,1]当前时间的进度,通过计算转换成实际的动画值
- if (animatedValue != null) {
- if (animatedValue instanceof Integer) {
- int start = (int) takeValues[0];
- int end = (int) takeValues[1];
- animatedValue = start + (int) (fraction * (end - start));
- } else {
- float start = (float) takeValues[0];
- float end = (float) takeValues[1];
- animatedValue = start + fraction * (end - start);
- }
- }
- currentAnimatedValue = animatedValue;
- // 将当前进度值通知给外部调用者
- if (updateListeners != null) {
- notifyOuterListener(animatorValue, fraction, animatedValue);
- }
- // 如果是组件动画,将动画值转换为视图组件的属性值变化
- if (targetHolder != null && targetHolder.get() != null) {
- updateComponentProperty((Float) animatedValue);
- }
- }
- };
- // 动画值转换为视图组件的属性变化
- private void updateComponentProperty(Float currentValue) {
- Component component = targetHolder.get();
- for (Property property : targetProperties) {
- switch (property) {
- case SCALE_X:// 缩放x
- component.setScaleX(currentValue);
- break;
- case SCALE_Y:// 缩放y
- component.setScaleY(currentValue);
- break;
- case TRANSLATION_X:// 平移x
- component.setTranslationX(currentValue);
- break;
- case TRANSLATION_Y:// 平移y
- component.setTranslationY(currentValue);
- break;
- case ALPHA:// 透明度
- component.setAlpha(currentValue);
- break;
- case ROTATION:// 中心旋转
- component.setRotation(currentValue);
- break;
- case WIDTH:// 尺寸宽
- float width = currentValue;
- component.setWidth((int) width);
- break;
- case HEIGHT:// 尺寸高
- float height = currentValue;
- component.setHeight((int) height);
- break;
- default:
- break;
- }
- }
- }
3.2 反向循环动画的实现
要执行反向循环动画,我们需要监听循环动画的每次循环节点,然后在下一次动画执行开始把动画的起始值反置。
- private static final int MAX_SIZE = 2;
- private final Object[] values = new Object[MAX_SIZE];// 正向动画起始值
- private final Object[] reverseValues = new Object[MAX_SIZE];// 反向动画起始值
- // 设置起始值时,除了正向动画起始值,同时构建一份反向起始值
- public void setFloatValues(float start, float end) {
- values[0] = start;
- values[1] = end;
- reverseValues[0] = end;
- reverseValues[1] = start;
- }
- // 监听动画循环点
- private final Animator.LoopedListener loopedListener = new Animator.LoopedListener() {
- @Override
- public void onRepeat(Animator animator) {
- // 如果循环模式设置为反向,下次执行动画则反向执行
- // 例如当前是[0,1],动画结束后就会从[1,0]开始执行,再下次又从[0,1],如此循环...
- if (takeReverseLogic) {
- isReversing = !isReversing;
- }
- if (listeners != null) {
- for (AnimatorListener listener : listeners) {
- listener.onAnimationRepeat(ValueAnimator.this);
- }
- }
- }
- };
- // 监听动画值的变化
- private final AnimatorValue.ValueUpdateListener valueUpdateListener = new AnimatorValue.ValueUpdateListener() {
- @Override
- public void onUpdate(AnimatorValue animatorValue, float fraction) {
- Object[] takeValues = values;
- if (takeReverseLogic && isReversing) {
- // 根据循环模式读取正向还是反向起始值
- takeValues = reverseValues;
- }
- ...
- };
- // 反向执行动画
- public void reverse() {
- takeReverseLogic = !takeReverseLogic;
- isReversing = !isReversing;
- // 先停止当前动画,然后再反向执行动画
- if (innerAnimator.isRunning()) {
- innerAnimator.end();
- }
- innerAnimator.start();
- }
3.3 动画操作的实现
因为我们核心的动画值计算是基于原生的ValueAnimator,因此我们基本的动画操作也是对其执行:
- private AnimatorValue innerAnimator;
- // 开始动画
- public void start() {
- if (innerAnimator.getLoopedCount() == AnimatorValue.INFINITE) {
- if (repeatMode == RepeatMode.REVERSE) {
- takeReverseLogic = true;
- }
- }
- // 对innerAnimator操作
- innerAnimator.start();
- }
- // 停止动画
- public void stop() {
- // 对innerAnimator操作
- innerAnimator.stop();
- }
- // 取消动画
- public void cancel() {
- // 对innerAnimator操作
- innerAnimator.cancel();
- }
- // 其他操作方法声明
- ...
总结
通过我们对原生AnimatorValue的扩展,我们实现了实际开发中大部分应用场景,让实际开发动画的效率可以大大提升。总结一下几点核心的扩展原理:
- 视图组件动画实现:监听原生动画值进行倍率转换,再设置给组件通用属性
- 反向/循环动画实现:监听循环动画的循环节点,反向赋值下一次动画的起始值