這可能是第二好的自定義 View 教程之屬性動畫

nanchen2251發表於2017-11-12

上期文章鎮樓:
這可能是第二好的自定義 View 教程之繪製

凱哥的文章確實寫的細而好呀,這不,活生生把 面試系列 先放一放,繼續講解我們的動畫。

為啥是第二好?

一看就是沒看 前面的文章 的。這裡就不解釋啦。

不是講所有動畫

Android 裡面對動畫可以進行一些分類,主要分為兩類:

  • Animation
  • Transition

由於 「Transtion」 重點在於切換而不是動畫,所以我們今天直接忽略。廢話不用多說,那麼我們就直接講解屬性動畫「Property Animation」吧。

現在的專案中的動畫 99% 都是用的屬性動畫,所以我們不講 View Animation。

ViewPropertyAnimator

這一塊比較簡單,我們可以直接引用凱哥這裡的總結圖。(凱哥文章,業界良心,真的很贊。)

圖片來自 HenCoder
圖片來自 HenCoder

從圖中可以看到, View 的每個方法都對應了 ViewPropertyAnimator 的兩個方法,其中一個是帶有 -By 字尾的,例如,View.setTranslationX() 對應了 ViewPropertyAnimator.translationX()ViewPropertyAnimator.translationXBy() 這兩個方法。其中帶有 -By() 字尾的是增量版本的方法,例如,translationX(100) 表示用動畫把 View 的 translationX 值漸變為 100,而 translationXBy(100) 則表示用動畫把 View 的 translationX 值 漸變地增加 100。

其中的 ViewPropertyAnimator 可以通過 View.animate() 得到。

/**
     * This method returns a ViewPropertyAnimator object, which can be used to animate
     * specific properties on this View.
     *
     * @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
     */
    public ViewPropertyAnimator animate() {
        if (mAnimator == null) {
            mAnimator = new ViewPropertyAnimator(this);
        }
        return mAnimator;
    }複製程式碼

ObjectAnimator

使用方式:

  • 如果是自定義控制元件,需要新增 setter / getter 方法;
  • ObjectAnimator.ofXXX() 建立 ObjectAnimator物件;
  • start() 方法執行動畫。

其中特別需要注意的是:

  • setter() 方法需要呼叫 invalidate() 對 View 進行重繪。
  • 獲取 ObjectAnimator 採用的是 ObjectAnimator.ofXXX() 方法。
    至於是 「ofFloat」還是「ofInt」還是別的,這個完全視你的 View 引數而定,並且第二個引數用 setXXX 的「XXX」字串。
  • 上面 ViewPropertyAnimator 方法基本都是通用的。
  • 當然,當你看到 ObjectAnimator.ofObject() 方法的時候,你會心生疑惑,這其實就是為了對不限定型別的屬性做動畫。

例子也懶得寫了,直接用凱哥的。

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();複製程式碼

圖片來自 HenCoder
圖片來自 HenCoder

當然,這些動畫都是可以自由組合的,支援「鏈式呼叫」,因為它們返回的都是 ViewPropertyAnimator

比如這樣。

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

圖片來自 HenCoder
圖片來自 HenCoder

而對於 ObjectAnimator,是不能這麼用的。不過你可以使用 PropertyValuesHolder 來同時在一個動畫中改變多個屬性。

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();複製程式碼

從上面的 gif 圖可以發現,動畫是同步進行的,那要是我們希望依次執行怎麼辦?比如這樣,先放大再平移。

圖片來自 HenCoder
圖片來自 HenCoder

萬能的 Android 自然難不倒我們,這樣就有了 AnimatorSet

AnimatorSet 多個動畫配合執行

AnimatorSet.playSequentially(Animator... items) 完美地解決了我們上面的疑惑。

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

其中 「animator1」和「animator2」分別是放大和平移的動畫。

翻閱官方原始碼一看,其實不止 playSequentially() 一個方法,除了順序執行,當然有其他方法,比如:

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(animator1).before(animator2); // 先執行 1 再執行 2
animatorSet.playTogether(animator2, animator3); // 2 和 3 同時開始
animatorSet.start();複製程式碼

類似的處理方案,還很多很多方式,具體你可以檢視官方原始碼。

僅靠這些方法做出來的動畫效果說實話已經很炫了,不過我們總是不滿足,比如我們設計師想這樣怎麼辦?

圖片來自 HenCoder
圖片來自 HenCoder

圖片中先是將進度填到了 100,再降回了實際的值。這利用上面所提到的知識,好像根本沒法實現,這效果有意思,我們看看怎麼實現。

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

除了合併多個屬性和調配多個動畫,你還可以在 PropertyValuesHolder 的基礎上更進一步,通過設定 Keyframe (關鍵幀),把同一個動畫屬性拆分成多個階段。比如,要實現上面的效果,你只需:

// 在 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();複製程式碼

先小結一下,「關於複雜的屬性關係來做動畫」,就這麼三種:

  • 使用 PropertyValuesHolder 來對多個屬性同時做動畫;
  • 使用 AnimatorSet 來同時管理調配多個動畫;
  • PropertyValuesHolder 的進階使用:使用 PropertyValuesHolder.ofKeyframe() 來把一個屬性拆分成多段,執行更加精細的屬性動畫。

ValueAnimator

實際上不太想說這個 ValueAnimator,因為它的使用場景確實不多。這裡也不精挑細琢了,大概需要記得:

ViewPropertyAnimatorObjectAnimator 的內部實現其實都是 ValueAnimatorObjectAnimator 更是本來就是 ValueAnimator 的子類,它們三個的效能並沒有差別。它們的差別只是使用的便捷性以及功能的靈活性。所以在實際使用時候的選擇,只要遵循一個原則就行:儘量用簡單的。能用 View.animate() 實現就不用 ObjectAnimator,能用 ObjectAnimator 就不用 ValueAnimator。

另外,它們還支援 setDuration(long duration) 設定動畫持續時長以及 setInterpolator() 設定各種「速度設定器」。

速度設定器?

「速度設定器」,簡而言之就是動畫的「速度模型」,這個方法可以讓動畫按照你想要的方式進行。一般情況我們都直接不設定個,所以下面的瞭解一下就好,甚至跳過也沒事。

簡單科普一下提供的「速度模型」,需要看效果的 點選這裡

  • AccelerateDecelerateInterpolator
    先加速再減速。這是預設的「速度模型」,實際上就像開車一樣,先加速啟動,開一會再減速剎車停下。

  • LinearInterpolator
    勻速模型,即動畫是迅速進行的。

  • AnticipateOvershootInterpolator
    帶施法前搖和回彈的「速度模型」。

  • DecelerateInterpolator
    持續減速到 0,動畫開始的時候是最高速度,然後在動畫過程中逐漸減速,直到動畫結束的時候恰好減速到 0。

  • AccelerateInterpolator
    持續加速。和上面那個剛剛相反。

  • AnticipateInterpolator
    先回拉一下再進行正常動畫軌跡。效果看起來有點像投擲物體或跳躍等動作前的蓄力。

  • OvershootInterpolator
    動畫會超過目標值一些,然後再彈回來。效果看起來有點像你一屁股坐在沙發上後又被彈起來一點的感覺。

  • AnticipateOvershootInterpolator
    上面這兩個的結合版:開始前回拉,最後超過一些然後回彈。

他孃的,太多啦,就不一一寫完了,真沒啥用處,用到再說嘛。

設定描述動畫生命週期的監聽器

可以給動畫設定監聽器,分別有兩種設定方式。

  • ViewPropertyAnimator.setListener(AnimatorListener listener)
  • ObjectAnimator.addListener(AnimatorListener listener)

可以看到,兩種動畫方式設定監聽器有一點不同的是一個是 「set」,一個是「add」,但它們的引數都是一致的「AnimatorListener」。

AnimatorListener 有四個回撥方法:

/**
         * <p>Notifies the start of the animation.</p>
         *
         * @param animation The started animation.
         */
        void onAnimationStart(Animator animation);

        /**
         * <p>Notifies the end of the animation. This callback is not invoked
         * for animations with repeat count set to INFINITE.</p>
         *
         * @param animation The animation which reached its end.
         */
        void onAnimationEnd(Animator animation);

        /**
         * <p>Notifies the cancellation of the animation. This callback is not invoked
         * for animations with repeat count set to INFINITE.</p>
         *
         * @param animation The animation which was canceled.
         */
        void onAnimationCancel(Animator animation);

        /**
         * <p>Notifies the repetition of the animation.</p>
         *
         * @param animation The animation which was repeated.
         */
        void onAnimationRepeat(Animator animation);複製程式碼

從程式碼中可以很清晰的看出:

  • 在動畫開始執行的時候,呼叫 onAnimationStart()
  • 在動畫結束後,將呼叫 onAnimationEnd()
  • 在動畫取消後,將呼叫 onAnimationCancel()
  • 在動畫重複執行的時候,將呼叫 onAnimationRepeat()

其中只需要注意兩點:

  • 動畫被 cancel() 取消的時候,依然會呼叫 onAnimationEnd(),不過是在 onAnimationCancel() 之後。
  • 重複執行動畫,通過 setRepeatMode() / setRepeatCount() 或者 repeat() 方法執行。
    但但是!!!ViewProperAnimator 不支援重複。

動畫的屬性更新監聽器

除了上面設定的動畫生命週期監聽器,我們還有其他的方法,比如 ViewPropertyAnimator.setUpdateListener() / ObjectAnimator.addUpdateListener()

這兩個方法雖然名稱和可設定的監聽器數量不一樣,但本質其實都一樣的,它們的引數都是 AnimatorUpdateListener。它只有一個回撥方法:onAnimationUpdate(ValueAnimator animation)

/**
     * Implementors of this interface can add themselves as update listeners
     * to an <code>ValueAnimator</code> instance to receive callbacks on every animation
     * frame, after the current frame's values have been calculated for that
     * <code>ValueAnimator</code>.
     */
    public static interface AnimatorUpdateListener {
        /**
         * <p>Notifies the occurrence of another frame of the animation.</p>
         *
         * @param animation The animation which was repeated.
         */
        void onAnimationUpdate(ValueAnimator animation);

    }複製程式碼

當動畫的屬性更新時(不嚴謹的說,即每過 10 毫秒,動畫的完成度更新時),這個方法被呼叫。

方法的引數是一個 ValueAnimatorValueAnimatorObjectAnimator 的父類,也是 ViewPropertyAnimator 的內部實現,所以這個引數其實就是 ViewPropertyAnimator 內部的那個 ValueAnimator,或者對於 ObjectAnimator 來說就是它自己本身。

小結

還是做個小結,自定義 View 中我們使用屬性動畫主要分為三種方式:

  • ViewPropertyAnimator
  • ObjectAnimator
  • ValueAnimator

使用它們的任一個都不會有效能差異,只需記住一個原則,依次越來越難,能用簡單的就用簡單的。

哦,還有一個最重要的原則就是,盯緊「扔物線凱哥」,加入我們的「HenCoder」大軍吧。

做不完的開源,寫不完的矯情。歡迎掃描下方二維碼或者公眾號搜尋「nanchen」關注我的微信公眾號,目前多運營 Android ,儘自己所能為你提升。如果你喜歡,為我點贊分享吧~
nanchen
nanchen

相關文章