Android動畫篇(四)—— 屬性動畫ValueAnimator的高級進階

前言:成功只屬於永不放棄的人。凡諸事有成者都有驚人的毅力做後盾,相信自己,持之以恆。

        上一篇文章給大家介紹了ValueAnimator的基本用法,都是比較簡單的用法,這裏帶大家學習一下有關加速器(Interpolator)、計算器(Evaluator)、ValueAnimator的ofObject用法等相關知識。

一、插值器(Interpolator)

         插值器(也叫加速器)有關的效果我在《Android動畫篇(二)—— 代碼生成alpha、scale、translate、rotate、set及插值器動畫》做了演示。

         什麼是插值器?我們知道通過ofInt(0,400)定義了動畫區間值爲0-400,然後通過添加AnimatorUpdateListener來監聽動畫實時變化,那麼這個變化是怎麼樣變的呢?是勻速變化還是有快有慢,如果我想先減速後加速,那麼該怎麼做呢?這就是插值器的作用,插值器就是用來控制動畫區間值如果計算出來的。比如LinearInterpolator是勻速計算返回區間點的值,而DecelerateInterpolator則是開始變快,後期變慢,其他都類似。

1、插值器的使用

我們使用彈跳插值器(BounceInterpolator)爲例做一個簡單的例子,BounceInterpolator就是在動畫結束的時候彈跳起來。

        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 400);
        valueAnimator.setDuration(1000);
        valueAnimator.setInterpolator(new BounceInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                mTextView.layout(mTextView.getLeft(), value, mTextView.getRight(), value + 
                mTextView.getHeight());
                Log.e(TAG, "value:" + value);
            }
        });
        valueAnimator.start();

效果圖如下:

如果大家看懂了上一篇文章,這裏的代碼就非常容易理解了。在監聽中,我只改變了top和bottom的位置,跟着動畫變化的值來改變top和bottom的值,setDuration(1000)設置了動畫時長,setInterpolator()設置了插值器,也就是過渡值的變化規則,插值器的意義其實就是相當於物理學中的加速度參數,這也是像加速器的原因。

2、自定義插值器

(1)概述

我們先看看最簡單的勻速插值器LinearInterpolator是怎麼樣的

@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}
abstract public class BaseInterpolator implements Interpolator {
    private @Config int mChangingConfiguration;
    /**
     * @hide
     */
    public @Config int getChangingConfiguration() {
        return mChangingConfiguration;
    }

    /**
     * @hide
     */
    void setChangingConfiguration(@Config int changingConfiguration) {
        mChangingConfiguration = changingConfiguration;
    }
}

        在上面的可以得知LinearInterpolator繼承了BaseInterpolator實現了Interpolator接口,而Interpolator 接口直接繼承自TimeInterpolator,而且Interpolator 沒有添加其他的方法,我們來看看TimeInterpolator這個接口:

/**
 * A time interpolator defines the rate of change of an animation. This allows animations
 * to have non-linear motion, such as acceleration and deceleration.
 */
public interface TimeInterpolator {

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);
}

   在TimeInterpolator裏面只有一個函數, float getInterpolation(float input):

  • float input   是float類型,範圍是0-1,表示當前動畫的進度,0表示動畫剛剛開始,1表示動畫結束。0.5表示動畫中間位的位                               置,其他的以此類推
  • 返回值        表示當前實際想要顯示的進度,取值可以大於1也可以小於0,大於1表示超出動畫位置,小於0表示小於開始位置

input參數表示當前動畫的進度,勻速增加的。動畫進度就是動畫時間上的進度,與我們任何設置無關,只與時間有關,隨時間的增加,動畫進度自然增加,從0到1,我們在setDuration(1000)設置了動畫時長的,動畫在該時間內完成。

而返回值表示動畫進度的具體數值,它的取值範圍是對應ofInt()、ofFloat()來指定的,這個返回值就表示當前時間動畫的具體進度值。

ValueAnimator valueAnimator = ValueAnimator.ofInt(100, 400);
        valueAnimator.setDuration(1000);
        valueAnimator.setInterpolator(new BounceInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                mTextView.layout(mTextView.getLeft(), value, mTextView.getRight(), value + mTextView.getHeight());
                Log.e(TAG, "value:" + value);
            }
        });
        valueAnimator.start();

從上面的代碼知道,我們設置了AnimatorUpdateListener後,在監聽回調函數中設置animation.getAnimatedValue()就可以獲得動畫具體進度值,那麼這個值是怎麼來的呢?

計算公式:當前進度值 = 100+(400-100)*顯示進度

其中100和400就是我們ofInt(100,400)設置的起點值和終點值,我們要計算位置是,在起始值100的基礎上加上實際運動距離(400-100)乘以運動進度。ofInt中的animation.getAnimatedValue()的值就是這麼來的。

我們知道了input與getInterpolation()返回值的關係了,來看看LinearInterpolator 是如何重寫getInterpolation()方法的?

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    ········

    public float getInterpolation(float input) {
        return input;
    }

    ········
}

LinearInterpolator 在getInterpolation()函數中,直接把input返回了,把動畫的進度作爲動畫具體的數值進度,也就是動畫進度和時間進度一致,比如動畫進度爲0,那麼動畫具體數值進度也爲0,動畫進度爲0.5,那麼動畫具體數值進度也爲0.5。

(2)實例

自定義插值器,繼承BaseInterpolator就可以了,實現其中的方法getInterpolation(float input),我們將該方法的返回值作了修改,我們將進度翻轉過來。然後使用我們的插值器。

public class MyInterpolator extends BaseInterpolator {
    @Override
    public float getInterpolation(float input) {
        return 1 - input;
    }
}

使用自定義的插值器

      ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 400);
        valueAnimator.setDuration(1000);
        //設置自定義的插值器
        valueAnimator.setInterpolator(new MyInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                mTextView.layout(mTextView.getLeft(), value, mTextView.getRight(), value + 
               mTextView.getHeight());
                Log.e(TAG, "value:" + value);
            }
        });
        valueAnimator.start();

效果如下:

我們在將進度數值倒敘返回後,TextView在結束爲止(400)返回到開始位置(0)。

到這裏,想必大家已經瞭解getInterpolation(float input)函數中input參數與返回值的關係,在重寫插值器時,需要強有力的數學知識做支持,一般都是通過數學公式來計算插值器的變化趨勢。你們可以在分析幾個其他插值器的寫法,可以總結成公式,放在公式插圖軟件裏面,看看對應的數學圖在(0,1)之間的走向,這個走勢就是插值器在數值變化時的樣子。

二、計算器(Evaluator)

1、概述

上面我們提到通過監聽器拿到當前動畫所對應的具體數值,而不是百分制的進度,那麼就有一個地方,根據當前的進度值轉化爲對應的數值,這個地方就是計算器(Evaluator),Evaluator就是將加速器返回的進度值轉化爲相對於的數值。其實Evaluator就是一個轉換器,他能把小數進度轉換成對應的數字值。

上面提到的公式:

當前值 = 100 + (400-100)*顯示進度

這個公式就是在Evaluator內計算的,在拿到當前數字進度所對應的值後將其返回。看看下面這幅圖:

上面這幅圖是從定義動畫的數值區間到通過AnimatorUpdateListener獲得當前動畫所對應具體數值的整個過程,解釋一下:

(1)ofInt(0,400)表示指定動畫的數值區間,是從0運動到400;

(2)在動畫開始之後,通過加速器返回動畫進度所對應的數字進度,這個數字進度是百分制的,以小數表示,比如0.2;

(3)Evaluator將加速器返回的進度值轉化爲相對於的數值;

(4)通過AnimatorUpdateListener監聽器使用animation.getAnimatedValue()函數獲得Evaluator中返回的數值。

2、各種Evaluator

無論是什麼動畫它的進度必然是在0-1之間,0表示沒開始,1表示結束,任何動畫都適用。但是Evaluator不一樣,我們知道Evaluator是用來根據加速器Interpolator返回的小數進度轉換成當前的數值進度。

那麼使用ofInt()來定義動畫,動畫中的值都是int類型,所對應的Evaluator返回值時,也是返回int類型的數值;如果使用ofFloat()來定義動畫,動畫中的值都是float類型,所對用的Evaluator返回值時,也是返回float類型的數值。所以每種Evaluator的定義方式必定有對應的Evaluator來使用,他的專用原因在於數值動畫類型不一致,在通過Evaluator返回時會報類型強轉錯誤。所以在動畫數值類型一致時,Evaluator才能通用,ofInt()對應IntEvaluator,ofFloat()對應FloatEvaluator,在設置Evaluator時通setEvaluator(TypeEvaluator value)來設置:

        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 400);
        valueAnimator.setDuration(1000);
        valueAnimator.setEvaluator(new IntEvaluator());
        valueAnimator.setInterpolator(new BounceInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                mTextView.layout(mTextView.getLeft(), value, mTextView.getRight(), value + 
         mTextView.getHeight());
                Log.e(TAG, "value:" + value);
            }
        });
        valueAnimator.start();

但是之前爲什麼沒有設置Evaluator的時候也能正常使用呢?因爲ofInt()和ofFloat()是系統直接提供的函數,在使用時都有默認的Interpolator和Evaluator,沒有設置則使用默認的,我們看看IntEvaluator的內部實現:

/**
 * This evaluator can be used to perform type interpolation between <code>int</code> values.
 */
public class IntEvaluator implements TypeEvaluator<Integer> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * <code>fraction</code> representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
     * and <code>t</code> is <code>fraction</code>.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type <code>int</code> or
     *                   <code>Integer</code>
     * @param endValue   The end value; should be of type <code>int</code> or <code>Integer</code>
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

可以看到IntEvaluator裏面只有一個函數evaluate(float fraction, Integer startValue, Integer endValue),其中fraction就是加速器中的返回值,表示當前動畫的數值進度,百分制小數表示。startValue和endValue分別對應ofInt(int start , int end)的起始值和結束值。比如上面的動畫ofInt(100,400)進行到40%時,在Evaluator函數中fraction=0.4,startValue = 100,endValue = 400;

下面我們看看進度小數值計算出來的具體數值:

return (int)(startInt + fraction * (endValue - startInt));

這不正是對應着我上面提到的公式嗎?

當前值 = 100 + (400-100)*顯示進度

計算原理我們在上面已經講過,根據進度來計算具體數值。我們來自定義一個Evaluator

3、自定義Evaluator

(1)簡單實現MyEvaluator

我們定義一個Evaluator,首先實現TypeEvaluator<>,根據數值類型傳入泛型類型:

public class MyEvaluator implements TypeEvaluator<Integer> {
    @Override
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        return null;
    }
}

我們來實現簡單的evaluate函數:

return (int) (200 + startValue + fraction * (endValue - startValue));

根據IntEvaluator的基礎上做了修改,讓返回值增加了200,所以開始定義的區間是ofInt(0,400)但是實際返回的區間是ofInt(200,600),看看怎麼使用:

        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 400);
        valueAnimator.setDuration(1000);
        valueAnimator.setEvaluator(new MyEvaluator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                mTextView.layout(mTextView.getLeft(), value, mTextView.getRight(), value + 
            mTextView.getHeight());
                Log.e(TAG, "value:" + value);
            }
        });
        valueAnimator.start();

效果圖如下:

從效果圖可看出TextView的動畫位置都向下移動了200。

在加速器Interpolator中,我們通過自定義加速器返回的數值進度來返回數值的位置,比如上面我們實現的倒敘動畫

在計算器Evaluator中,通過改變進度值所對應的具體數值來改變數值位置。

結論:我們可以通過重寫加速器Interpolator來改變數值進度來改變數值位置,也可以通多改變計算器Evaluator中進度所對應的數值來改變數值位置。

(2)實現倒敘輸出

我們來定義一個ReverseEvaluator來實現倒敘:

public class ReverseEvaluator implements TypeEvaluator<Integer> {
    @Override
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        return (int) (endValue - fraction * (endValue - startValue));
    }
}

其中fraction * (endValue - startValue)表示實際運動的距離,endValue - fraction * (endValue - startValue)表示離終點的距離有多少,實現了從終點出發,最終到達起點。我們使用試一試:

 valueAnimator.setEvaluator(new ReverseEvaluator());

效果如下:

4、關於ArgbEvaluator

ArgbEvaluator使用來做顏色值轉換的,我們先來使用一下,最後再講源碼:

在源碼我們可以看出返回值時Int類型,可以使用ofInt()來初始化動畫的取值範圍,顏色值包括A、R、G、B四個值,每個值都是8位,用16進制來表示顏色值。

        ValueAnimator valueAnimator = ValueAnimator.ofInt(0xff000000, 0xffffffff);
        valueAnimator.setDuration(2000);
        valueAnimator.setEvaluator(new ArgbEvaluator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                mTextView.setBackgroundColor(value);
                Log.e(TAG, "value:" + value);
            }
        });
        valueAnimator.start();

我們將動畫的取值範圍定義爲ofInt(0xff000000, 0xffffffff),即從黑色變爲白色,在AnimatorUpdateListener中將回傳回來的顏色值設置在TextView的背景顏色中。

點擊進去看一看ArgbEvaluator的源碼:

  /**
     * This function returns the calculated in-between value for a color
     * given integers that represent the start and end values in the four
     * bytes of the 32-bit int. Each channel is separately linearly interpolated
     * and the resulting calculated values are recombined into the return value.
     *
     * @param fraction The fraction from the starting to the ending values
     * @param startValue A 32-bit int value representing colors in the
     * separate bytes of the parameter
     * @param endValue A 32-bit int value representing colors in the
     * separate bytes of the parameter
     * @return A value that is calculated to be the linearly interpolated
     * result, derived by separating the start and end values into separate
     * color channels and interpolating each one separately, recombining the
     * resulting values in the same way.
     */
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        int startInt = (Integer) startValue;
        float startA = ((startInt >> 24) & 0xff) / 255.0f;
        float startR = ((startInt >> 16) & 0xff) / 255.0f;
        float startG = ((startInt >>  8) & 0xff) / 255.0f;
        float startB = ( startInt        & 0xff) / 255.0f;

        int endInt = (Integer) endValue;
        float endA = ((endInt >> 24) & 0xff) / 255.0f;
        float endR = ((endInt >> 16) & 0xff) / 255.0f;
        float endG = ((endInt >>  8) & 0xff) / 255.0f;
        float endB = ( endInt        & 0xff) / 255.0f;

        // convert from sRGB to linear
        startR = (float) Math.pow(startR, 2.2);
        startG = (float) Math.pow(startG, 2.2);
        startB = (float) Math.pow(startB, 2.2);

        endR = (float) Math.pow(endR, 2.2);
        endG = (float) Math.pow(endG, 2.2);
        endB = (float) Math.pow(endB, 2.2);

        // compute the interpolated color in linear space
        float a = startA + fraction * (endA - startA);
        float r = startR + fraction * (endR - startR);
        float g = startG + fraction * (endG - startG);
        float b = startB + fraction * (endB - startB);

        // convert back to sRGB in the [0..255] range
        a = a * 255.0f;
        r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
        g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
        b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;

        return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
    }

這段代碼主要分爲三部分:

(1)根據startValue求出A、R、G、B中各個色彩的初始值;

(2)根據endValue求出A、R、G、B的各個結束值;

(3)根據當前動畫百分比進度求出對應的值。

第一部分:根據startValue求出A、R、G、B中各個色彩的初始值

        int startInt = (Integer) startValue;
        float startA = ((startInt >> 24) & 0xff) / 255.0f;
        float startR = ((startInt >> 16) & 0xff) / 255.0f;
        float startG = ((startInt >>  8) & 0xff) / 255.0f;
        float startB = ( startInt        & 0xff) / 255.0f;

這段代碼是根據位移和與運算計算出顏色值中A、R、G、B各部分對應的值,顏色值與A、R、G、B值關係如下:

Android中的顏色值遵循RGB/ARGB標準,使用時通常以"#"爲爲開頭的8位16進製表示。其中ARGB依次代表透明度(alpha)、紅色(red)、綠色(green)、藍色(blue);取值範圍爲0~255,即十六進制的0x00~0xff表示。A從0x00到0xff表示完全透明到完全不透明,RGB從0x00到0xff表示顏色從淺到深。

所以我們的初始值是0xff000000,求出來的startA = 0xff,startR = 0x00,startG = 0x00,startB = 0x00;關於位移和與運算我就不講了。

第二部分:和第一部分原理一致,根據endValue求出A、R、G、B的各個結束值的顏色值:

        int endInt = (Integer) endValue;
        float endA = ((endInt >> 24) & 0xff) / 255.0f;
        float endR = ((endInt >> 16) & 0xff) / 255.0f;
        float endG = ((endInt >>  8) & 0xff) / 255.0f;
        float endB = ( endInt        & 0xff) / 255.0f;

我們的結束值是:0xffffffff,求出來的endA = oxff,endR = oxff,endG = oxff,endB = oxff;

最後一部分:根據當前動畫百分比進度求出對應的值

        float a = startA + fraction * (endA - startA);
        float r = startR + fraction * (endR - startR);
        float g = startG + fraction * (endG - startG);
        float b = startB + fraction * (endB - startB);

對於這個計算都很容易理解,其實和Evalustor的計算公式一樣,根據起始值和結束值計算出該進度下的具體值。最後通過位移和與運算將當下的ARGB各個值組合成當前的顏色值。

三、ofObject

1、ofObject的概述

前面講了ValueAnimator使用ofInt()和ofFloat()來定義動畫,但是ofInt()和ofFloat()分別只能傳入Int和Float類型的參數,如果我們需要其他操作類型的怎麼辦?ValueAnimator還有一個ofObject()類型的參數,可以傳入任何類型的變量:

public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)

它有兩個參數,一個是自定義的Evaluator;一個是可變長參數,object類型

大家可能有疑問,爲什麼要自定義Evaluator,因爲Evaluator是根據動畫當前的進度來計算當前對應的值的,如果可變參數類型是object,那麼具體的類型就是未知的,所以Evaluator無法根據類型來得出結果,進度值的轉換過程也必須由我們來做,我們來看看ofObject()的使用效果:

從效果圖可以看出,字母A變化到Z,並且變化速度越來越快。

 ValueAnimator valueAnimator = ValueAnimator.ofObject(new CharEvaluator(), new Character('A'), new Character('Z'));
        valueAnimator.setDuration(6000);
        valueAnimator.setInterpolator(new AccelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                char value = (char) animation.getAnimatedValue();
                mTextView.setText(String.valueOf(value));
                Log.e(TAG, "value:" + value);
            }
        });
        valueAnimator.start();

這裏注意三點:

(1)構造器:

ValueAnimator.ofObject(new CharEvaluator(), new Character('A'), new Character('Z'));

我們定義了一個CharEvaluator(後面會講),初始化動畫時,傳入Character對象,起始是A,結束是Z,利用動畫自動變化字母A到字母Z,具體的實現就是CharEvaluator的事情了;

(2)AnimatorUpdateListener監聽

      char value = (char) animation.getAnimatedValue();
      mTextView.setText(String.valueOf(value));

通過animation.getAnimatedValue()得到當前動畫字符,在動畫過程中通過Evaluator返回值的類型必然和構造時的類型時一樣的,由於傳入的是Character,所以得到的也是Character,轉爲字符串後設置到TextView;

(3)插值器

valueAnimator.setInterpolator(new AccelerateInterpolator());

這裏我們使用的加速插值器,隨着動畫的進行,速度會越來越快。

好,最關鍵的地方到了,CharEvaluator是怎麼實現的呢?ASCII碼錶中的數值與字符轉換的方法,每一個字符都有一個跟它相對應的數值,字母A到字母Z所對應的數值區間爲65-90,在程序中我們可以通過強轉來改變類型。

比如:數字轉字符

char temp = (char)65;//得到的temp的值是大寫字母A

字符轉數值

    char temp = 'A';
    int num = (int)temp;//這裏的到的數值就是ASCII碼錶中A對應的值65

CharEvaluator的實現:

public class CharEvaluator implements TypeEvaluator<Character> {
    @Override
    public Character evaluate(float fraction, Character startValue, Character endValue) {
        int startInt = (int) startValue;
        int endInt = (int) endValue;
        int value = (int) (startInt + fraction * (endInt - startInt));
        return (char) value;
    }
}

在這裏,我們就是利用A-Z字母在ASCII字碼表中數值遞增的原理,先求出對應字符的數值,再將數值轉化爲具體字符。

2、ofObject的自定義對象

上面我們初度使用了ofObject(),ofObject()能初始化任何對像,我們來自定義一個對象,然後利用ofObject()來構造這個對象的動畫。

在這裏,我們自定義一個view,在view上畫一個圓,有動畫效果,插值器使用的是回彈插值器(BounceInterpolator)

(1)定義類Point

public class Point {
    private int radius;

    public Point(int radius) {
        this.radius = radius;
    }

    public int getRadius() {
        return radius;
    }

    public void setRadius(int radius) {
        this.radius = radius;
    }
}

point類很簡單,只有一個成員變量radius

(2)自定義MyPointView

public class MyPointView extends View {
    private Point mPoint;

    public MyPointView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mPoint != null) {
            Paint paint = new Paint();
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.FILL);
            paint.setAntiAlias(true);
            canvas.drawCircle(500f, 600f, mPoint.getRadius(), paint);
        }
    }

    public void doAnimator() {
        ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(), new Point(20), new Point(300));
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPoint = (Point) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.setDuration(1000);
        valueAnimator.setInterpolator(new BounceInterpolator());
        valueAnimator.start();
    }
}

我們先來理一理這裏的代碼:先看看外部調用的動畫函數

public void doAnimator() {
        ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(), new Point(20), new Point(300));
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPoint = (Point) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.setDuration(1000);
        valueAnimator.setInterpolator(new BounceInterpolator());
        valueAnimator.start();
    }

ofObject()的構造方法:

ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(), new Point(20), new Point(300));

這裏可以看到,在構造動畫時,動畫所對應的值是Point類型,那麼在Evaluator中的返回值類型也是Point,有關PointEvaluator下面再講,下面看看監聽中的代碼:

 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPoint = (Point) animation.getAnimatedValue();
                invalidate();
            }
        });

在監聽過程中,通過animation.getAnimatedValue()得到的mPoint實例,保存在成員變量中,然後 invalidate()強制刷新View。在強制刷新後,會走onDraw()方法:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mPoint != null) {
            Paint paint = new Paint();
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.FILL);
            paint.setAntiAlias(true);
            canvas.drawCircle(500f, 600f, mPoint.getRadius(), paint);
        }
    }

這裏設置畫筆的屬性,在(500,600)的位置畫出圓形。在構造ofObject()函數中,初始值和中間值都是Point類型,PointEvaluator的返回值類型也是Point:

public class PointEvaluator implements TypeEvaluator<Point> {
    @Override
    public Point evaluate(float fraction, Point startPoint, Point endPoint) {
        int startInt = startPoint.getRadius();
        int endInt = endPoint.getRadius();
        int value = (int) (startInt + fraction * (endInt - startInt));
        return new Point(value);
    }
}

這裏根據初始半徑和最終半徑求出當前動畫所對應的半徑值,然後構建一個Point對象。

(3)使用MyPointView

在佈局中添加控件:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_start_anim"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#666666"
        android:padding="10dp"
        android:text="start anim"/>

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:background="@color/colorAccent"
        android:padding="10dp"
        android:text="Hello World!"
        android:textColor="#ffffff"/>

    <TextView
        android:id="@+id/tv_cancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:background="#666666"
        android:padding="10dp"
        android:text="cancel anim"
        android:textColor="#ffffff"/>

    <com.example.a04_valueanimator_advanced.MyPointView
        android:id="@+id/myPointView"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

調用控件的屬性:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = MainActivity.class.getSimpleName();
    private TextView mTextView;
    private MyPointView myPointView;
    private ValueAnimator mValueAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myPointView = findViewById(R.id.myPointView);
        mTextView = findViewById(R.id.tv);
        findViewById(R.id.tv_start_anim).setOnClickListener(this);
        findViewById(R.id.tv_cancel).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_start_anim:
                setMyPointView();
                break;
            case R.id.tv_cancel:
                if (mValueAnimator != null) {
                    //退出動畫
                    mValueAnimator.cancel();
                }
                break;
            default:
                break;
        }
    }

    /**
     * ofObject的使用2
     */
    private void  setMyPointView() {
        myPointView.doAnimator();
    }
}

點擊start anim按鈕時調用myPointView.doAnimator()方法開始動畫。

至此,本文結束!篇幅比較長。

源碼下載地址:https://github.com/FollowExcellence/AndroidAnimation

請大家尊重原創者版權,轉載請標明出處: https://blog.csdn.net/m0_37796683/article/details/90483462 謝謝!

動畫系列文章:

1、 Android動畫篇(一)—— alpha、scale、translate、rotate、set的xml屬性及用法

  • 補間動畫的XML用法以及屬性詳解

2、Android動畫篇(二)—— 代碼實現alpha、scale、translate、rotate、set及插值器動畫

  • 代碼動態實現補間動畫以及屬性詳解

3、 Android動畫篇(三)—— 屬性動畫ValueAnimator的使用

  • ValueAnimator的基本使用

4、 Android動畫篇(四)—— 屬性動畫ValueAnimator的高級進階

  • 插值器(Interpolator)、計算器(Evaluator)、ValueAnimator的ofObject用法等相關知識

5、 Android動畫篇(五)—— 屬性動畫ObjectAnimator基本使用

  • ObjectAnomator的基本使用以及屬性詳解

6、 Android動畫篇(六)—— 組合動畫AnimatorSet和PropertyValuesHolder的使用

  • AnimatorSet動畫集合和PropertyValuesHolder的使用

以上幾篇動畫文章是一定要掌握的,寫的不好請多多指出!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章