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的擴充套件,我們實現了實際開發中大部分應用場景,讓實際開發動畫的效率可以大大提升。總結一下幾點核心的擴充套件原理:
- 檢視元件動畫實現:監聽原生動畫值進行倍率轉換,再設定給元件通用屬性
- 反向/迴圈動畫實現:監聽迴圈動畫的迴圈節點,反向賦值下一次動畫的起始值
「其他文章」
- Nature子刊 | NUS、位元組首次將AI元學習引入腦成像領域
- 十個有趣的高階Python指令碼,建議收藏
- 網際網路通訊安全之終端資料保護
- 實用!一款開源的 JSON 視覺化管理工具
- 在 React 中實現條件渲染的七種方法
- 使用 Node-RED 處理 MQTT 資料
- Spring Cloud OpenFeign 的五個優化小技巧!
- 閒魚一面:Thread.sleep(0) 到底有什麼用?
- 介紹 Pandas 實戰中一些高階玩法
- Linux容器技術的實現原理
- 基於DolphinDB的因子計算最佳實踐
- 2013年圖靈獎得主 Leslie Lamport 專訪:程式設計師需要更多的數學知識
- 青雲儲存全面升級,自研QingStor U10000釋放更多資料潛能
- 諾!給你「最酷」網頁設計指南
- 如何應對網路中斷的噩夢
- Meta高效能叢集網路架構之路
- 位運算的秒用--異或運算面試真題
- RocketMQ 5.0: 儲存計算分離新思路
- 2022年值得使用的 Node.js 框架
- HTTP 的快取為什麼這麼設計?