Android 自定義View:屬性動畫(六)

zeroXuan發表於2019-05-08

ViewPropertyAnimator

使用方式:View.animate() 後跟 translationX() 等方法,動畫會自動執行

view.animate().translationX(500);
複製程式碼

ObjectAnimator

使用方式:

  • 如果是自定義控制元件,需要新增 setter / getter 方法,並在setter方法的最後呼叫invalidate()方法,重新整理繪製;
  • 用 ObjectAnimator.ofXXX() 建立 ObjectAnimator 物件;
  • 用 start() 方法執行動畫。
public class SportsView extends View {

    float progress = 0;

    ......

    // 建立 getter 方法
    public float getProgress() {
        return progress;
    }

    // 建立 setter 方法
    public void setProgress(float progress) {
        this.progress = progress;
        invalidate();
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        ......

        canvas.drawArc(arcRectF, 135, progress * 2.7f, false, paint);

        ......
    }
}

......

// 建立 ObjectAnimator 物件
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "progress", 0, 65);
// 執行動畫
animator.start();
複製程式碼

設定監聽器

給動畫設定監聽器,可以在關鍵時刻得到反饋,從而及時做出合適的操作,例如在動畫的屬性更新時同步更新其他資料,或者在動畫結束後回收資源等。

設定監聽器的方法, ViewPropertyAnimatorObjectAnimator 略微不一樣: ViewPropertyAnimator用的是 setListener()setUpdateListener()方法,可以設定一個監聽器,要移除監聽器時通過 set[Update]Listener(null) 填 null 值來移除;而 ObjectAnimator則是用 addListener()addUpdateListener() 來新增一個或多個監聽器,移除監聽器則是通過 remove[Update]Listener() 來指定移除物件。

另外,由於 ObjectAnimator 支援使用 pause()方法暫停,所以它還多了一個 addPauseListener() / removePauseListener() 的支援; ViewPropertyAnimator 則獨有 withStartAction()withEndAction() 方法,可以設定一次性的動畫開始或結束的監聽,在動畫執行結束後就自動丟棄,就算之後再重用 ViewPropertyAnimator 來做別的動畫,用它們設定的回撥也不會再被呼叫。而 set/addListener() 所設定的 AnimatorListener 是持續有效的,當動畫重複執行時,回撥總會被呼叫。

需要說明一下的是,就算動畫被取消,onAnimationEnd() 也會被呼叫。所以當動畫被取消時,如果設定了 AnimatorListener,那麼 onAnimationCancel()onAnimationEnd() 都會被呼叫。onAnimationCancel() 會先於 onAnimationEnd() 被呼叫。

withEndAction() 設定的回撥只有在動畫正常結束時才會被呼叫,而在動畫被取消時不會被執行。這點和 AnimatorListener.onAnimationEnd() 的行為是不一致的。

TypeEvaluator

關於 ObjectAnimator,上面講到可以用 ofInt() 來做整數的屬性動畫和用ofFloat() 來做小數的屬性動畫。這兩種屬性型別是屬性動畫最常用的兩種,不過在實際的開發中,可以做屬相動畫的型別還是有其他的一些型別。當需要對其他型別來做屬性動畫的時候,就需要用到 TypeEvaluator 了。

自定義 Evaluator

藉助於 TypeEvaluator,屬性動畫就可以通過 ofObject()來對不限定型別的屬性做動畫了。方式很簡單:

private class PointFEvaluator implements TypeEvaluator<PointF> {
   PointF newPoint = new PointF();

   @Override
   public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
       float x = startValue.x + (fraction * (endValue.x - startValue.x));
       float y = startValue.y + (fraction * (endValue.y - startValue.y));

       newPoint.set(x, y);

       return newPoint;
   }
}

ObjectAnimator animator = ObjectAnimator.ofObject(view, "position",
        new PointFEvaluator(), new PointF(0, 0), new PointF(1, 1));
animator.start();
複製程式碼

PropertyValuesHolder 同一個動畫中改變多個屬性

很多時候,你在同一個動畫中會需要改變多個屬性,例如在改變透明度的同時改變尺寸。如果使用 ViewPropertyAnimator,你可以直接用連寫的方式來在一個動畫中同時改變多個屬性:

view.animate()
        .scaleX(1)
        .scaleY(1)
        .alpha(1);
複製程式碼

ObjectAnimator 同一個動畫中改變多個屬性

使用 PropertyValuesHolder 來同時在一個ObjectAnimator動畫中改變多個屬性。

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);

ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3)
animator.start();
複製程式碼

AnimatorSet 多個動畫配合執行

有的時候,你不止需要在一個動畫中改變多個屬性,還會需要多個動畫配合工作,比如,在內容的大小從 0 放大到 100% 大小後開始移動。這種情況使用 PropertyValuesHolder 是不行的,因為這些屬性如果放在同一個動畫中,需要共享動畫的開始時間、結束時間、Interpolator 等等一系列的設定,這樣就不能有先後次序地執行動畫了。

這就需要用到 AnimatorSet 了。

ObjectAnimator animator1 = ObjectAnimator.ofFloat(...);
animator1.setInterpolator(new LinearInterpolator());
ObjectAnimator animator2 = ObjectAnimator.ofInt(...);
animator2.setInterpolator(new DecelerateInterpolator());

AnimatorSet animatorSet = new AnimatorSet();
// 兩個動畫依次執行
animatorSet.playSequentially(animator1, animator2);
animatorSet.start();


// 兩個動畫同時執行
animatorSet.playTogether(animator1, animator2);
animatorSet.start();


// 使用 AnimatorSet.play(animatorA).with/before/after(animatorB)
// 的方式來精確配置各個 Animator 之間的關係
animatorSet.play(animator1).with(animator2);
animatorSet.play(animator1).before(animator2);
animatorSet.play(animator1).after(animator2);
animatorSet.start();
複製程式碼

PropertyValuesHolders.ofKeyframe() 把同一個屬性拆分

// 在 0% 處開始
Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
// 時間經過 50% 的時候,動畫完成度 100%
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
// 時間見過 100% 的時候,動畫完成度倒退到 80%,即反彈 20%
Keyframe keyframe3 = Keyframe.ofFloat(1, 80);

PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3);

ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder);
animator.start();
複製程式碼

ValueAnimator 最基本的輪子

額外簡單說一下 ValuesAnimator。很多時候,你用不到它,只是在你使用一些第三方庫的控制元件,而你想要做動畫的屬性卻沒有 setter / getter 方法的時候,會需要用到它。

ValueAnimator 並不常用,因為它的功能太基礎了。ValueAnimator 是 ObjectAnimator 的父類,實際上,ValueAnimator 就是一個不能指定目標物件版本的 ObjectAnimator。

ObjectAnimator 是自動呼叫目標物件的 setter 方法來更新目標屬性的值,以及很多的時候還會以此來改變目標物件的 UI,而 ValueAnimator 只是通過漸變的方式來改變一個獨立的資料,這個資料不是屬於某個物件的,至於在資料更新後要做什麼事,全都由你來定,你可以依然是去呼叫某個物件的 setter 方法(別這麼為難自己),也可以做其他的事,不管要做什麼,都是要你自己來寫的,ValueAnimator 不會幫你做。

比如有的時候,你要給一個第三方控制元件做動畫,你需要更新的那個屬性沒有 setter 方法,只能直接修改,這樣的話 ObjectAnimator 就不靈了啊。怎麼辦?這個時候你就可以用 ValueAnimator,在它的 onUpdate() 裡面更新這個屬性的值,並且手動呼叫 invalidate()

所以,ViewPropertyAnimator、ObjectAnimator、ValueAnimator 這三種 Animator,它們其實是一種遞進的關係:從左到右依次變得更加難用,也更加靈活。但我要說明一下,它們的效能是一樣的,因為 ViewPropertyAnimator 和 ObjectAnimator 的內部實現其實都是 ValueAnimator,ObjectAnimator 更是本來就是 ValueAnimator 的子類,它們三個的效能並沒有差別。它們的差別只是使用的便捷性以及功能的靈活性。所以在實際使用時候的選擇,只要遵循一個原則就行:儘量用簡單的。

能用 View.animate() 實現就不用 ObjectAnimator,能用 ObjectAnimator 就不用 ValueAnimator。

目錄結構

參考

相關文章