HarmonyOS 屬性動畫擴展
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
簡介
HarmonyOS 提供了AnimatorValue來執(zhí)行屬性動畫,但是其方法數(shù)很少,并且屬性值范圍局限于[0,1],也不直接支持動畫反轉(zhuǎn)等一些常用的操作。在日常開發(fā)中,我們經(jīng)常需要基于這個類進行擴展編寫,去適應(yīng)真實場景。因此,通過收集常用的場景,整理出一個屬性動畫的擴展類ValueAnimator。
效果演示

實現(xiàn)思路
1. 動畫分類
實際開發(fā)過程,我們大部分的動畫都是作用于視圖組件。任何復(fù)雜的動畫,通過逐幀分解,最終都可以歸納為如下幾種基礎(chǔ)動畫的組合:
- X,Y軸縮放動畫
- X,Y軸平移動畫
- 透明度動畫
- 旋轉(zhuǎn)角度動畫
- 組件寬高尺寸動畫
2. 動畫操作
對于動畫的操作,我們可以歸納出以下這些動作:
- 開始動畫
- 暫停動畫
- 取消動畫
- 結(jié)束動畫
- 反轉(zhuǎn)動畫
- 設(shè)置動畫起始值
- 設(shè)置動畫延時
- 設(shè)置動畫執(zhí)行時長
- 設(shè)置動畫插值器
- 設(shè)置動畫狀態(tài)監(jiān)聽
- 設(shè)置動畫進度監(jiān)聽
- 設(shè)置動畫執(zhí)行次數(shù)
- 設(shè)置動畫重復(fù)模式
- 設(shè)置組件動畫屬性值
3. 代碼實現(xiàn)
3.1 視圖動畫的實現(xiàn)
系統(tǒng)原生提供的AnimatorValue為我們提供了[0,1]的動畫范圍。因此最簡單的實現(xiàn)方式是定義一個ValueAnimator,內(nèi)部包含一個系統(tǒng)的AnimatorValue來計算[0,1]的進度值,通過自己的邏輯轉(zhuǎn)換為我們期望的動畫值。
- public class ValueAnimator {
- private AnimatorValue innerAnimator;
- // 監(jiān)聽動畫值的變化
- private final AnimatorValue.ValueUpdateListener valueUpdateListener = new AnimatorValue.ValueUpdateListener() {
- @Override
- public void onUpdate(AnimatorValue animatorValue, float fraction) {
- Object[] takeValues = values;
- // 動畫反轉(zhuǎn)運算處理
- if (takeReverseLogic && isReversing) {
- takeValues = reverseValues;
- }
- Object animatedValue = takeValues[0];
- // fraction為[0,1]當(dāng)前時間的進度,通過計算轉(zhuǎn)換成實際的動畫值
- 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;
- // 將當(dāng)前進度值通知給外部調(diào)用者
- if (updateListeners != null) {
- notifyOuterListener(animatorValue, fraction, animatedValue);
- }
- // 如果是組件動畫,將動畫值轉(zhuǎn)換為視圖組件的屬性值變化
- if (targetHolder != null && targetHolder.get() != null) {
- updateComponentProperty((Float) animatedValue);
- }
- }
- };
- // 動畫值轉(zhuǎn)換為視圖組件的屬性變化
- 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:// 中心旋轉(zhuǎn)
- 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 反向循環(huán)動畫的實現(xiàn)
要執(zhí)行反向循環(huán)動畫,我們需要監(jiān)聽循環(huán)動畫的每次循環(huán)節(jié)點,然后在下一次動畫執(zhí)行開始把動畫的起始值反置。
- private static final int MAX_SIZE = 2;
- private final Object[] values = new Object[MAX_SIZE];// 正向動畫起始值
- private final Object[] reverseValues = new Object[MAX_SIZE];// 反向動畫起始值
- // 設(shè)置起始值時,除了正向動畫起始值,同時構(gòu)建一份反向起始值
- public void setFloatValues(float start, float end) {
- values[0] = start;
- values[1] = end;
- reverseValues[0] = end;
- reverseValues[1] = start;
- }
- // 監(jiān)聽動畫循環(huán)點
- private final Animator.LoopedListener loopedListener = new Animator.LoopedListener() {
- @Override
- public void onRepeat(Animator animator) {
- // 如果循環(huán)模式設(shè)置為反向,下次執(zhí)行動畫則反向執(zhí)行
- // 例如當(dāng)前是[0,1],動畫結(jié)束后就會從[1,0]開始執(zhí)行,再下次又從[0,1],如此循環(huán)...
- if (takeReverseLogic) {
- isReversing = !isReversing;
- }
- if (listeners != null) {
- for (AnimatorListener listener : listeners) {
- listener.onAnimationRepeat(ValueAnimator.this);
- }
- }
- }
- };
- // 監(jiān)聽動畫值的變化
- private final AnimatorValue.ValueUpdateListener valueUpdateListener = new AnimatorValue.ValueUpdateListener() {
- @Override
- public void onUpdate(AnimatorValue animatorValue, float fraction) {
- Object[] takeValues = values;
- if (takeReverseLogic && isReversing) {
- // 根據(jù)循環(huán)模式讀取正向還是反向起始值
- takeValues = reverseValues;
- }
- ...
- };
- // 反向執(zhí)行動畫
- public void reverse() {
- takeReverseLogic = !takeReverseLogic;
- isReversing = !isReversing;
- // 先停止當(dāng)前動畫,然后再反向執(zhí)行動畫
- if (innerAnimator.isRunning()) {
- innerAnimator.end();
- }
- innerAnimator.start();
- }
3.3 動畫操作的實現(xiàn)
因為我們核心的動畫值計算是基于原生的ValueAnimator,因此我們基本的動畫操作也是對其執(zhí)行:
- 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();
- }
- // 其他操作方法聲明
- ...
總結(jié)
通過我們對原生AnimatorValue的擴展,我們實現(xiàn)了實際開發(fā)中大部分應(yīng)用場景,讓實際開發(fā)動畫的效率可以大大提升??偨Y(jié)一下幾點核心的擴展原理:
- 視圖組件動畫實現(xiàn):監(jiān)聽原生動畫值進行倍率轉(zhuǎn)換,再設(shè)置給組件通用屬性
- 反向/循環(huán)動畫實現(xiàn):監(jiān)聽循環(huán)動畫的循環(huán)節(jié)點,反向賦值下一次動畫的起始值
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)