HarmonyOS 屬性動畫擴充套件

語言: CN / TW / HK

簡介

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的擴充套件,我們實現了實際開發中大部分應用場景,讓實際開發動畫的效率可以大大提升。總結一下幾點核心的擴充套件原理:

  • 檢視元件動畫實現:監聽原生動畫值進行倍率轉換,再設定給元件通用屬性
  • 反向/迴圈動畫實現:監聽迴圈動畫的迴圈節點,反向賦值下一次動畫的起始值

想了解更多內容,請訪問:

51CTO和華為官方合作共建的鴻蒙技術社群

https://harmonyos.51cto.com