HarmonyOS 屬性動畫擴展
簡介
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的擴展,我們實現了實際開發中大部分應用場景,讓實際開發動畫的效率可以大大提升。總結一下幾點核心的擴展原理:
- 視圖組件動畫實現:監聽原生動畫值進行倍率轉換,再設置給組件通用屬性
- 反向/循環動畫實現:監聽循環動畫的循環節點,反向賦值下一次動畫的起始值