自定義View(四)-動畫- Interpolator與Evaluator

介紹

Interpolator插值器之前我們已經接觸過了,而Evaluator好像我們還沒有將,這是屬性動畫中倆個比較中的兩個知識點,弄清楚它們有助於我們更好的使用與理解屬性動畫。


Interpolator插值器

  • 分析

之前我們已經明白了它的作用了,他就是一個控制動畫如何運動的一個工具。比如有勻速效果插值器,回彈效果的插值器等等。現在我們就來從源碼的角度分析下看看他是如何實現的。我們以LinearInterpolator來分析插值器(在api22以後繼承BaseInterpolator,但是邏輯是一樣的)

public class LinearInterpolator extends Interpolator   {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

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

LinearInterpolator實現了Interpolator接口;而Interpolator接口則直接繼承自TimeInterpolator,而且並沒有添加任何其它的方法。
那我們來看看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);我們來講講這個函數是幹什麼的。
參數input: input參數是一個float類型,它取值範圍是0到1,表示當前動畫的進度,取0時表示動畫剛開始,取1時表示動畫結束,取0.5時表示動畫中間的位置,其它類推。
返回值: 表示當前實際想要顯示的進度。取值可以超過1也可以小於0,超過1表示已經超過目標值,小於0表示小於開始位置。正是因爲每種插值器返回值不同才形成了不同運動效果的動畫
對於input參數,它表示的是當前動畫的進度,勻速增加的。什麼叫動畫的進度,動畫的進度就是動畫在時間上的進度,與我們的任何設置無關,隨着時間的增長,動畫的進度自然的增加,從0到1;input參數相當於時間的概念,我們通過setDuration()指定了動畫的時長,在這個時間範圍內,動畫進度肯定是一點點增加的;就相當於我們播放一首歌,這首歌的進度是從0到1是一樣的。
而返回值則表示動畫的數值進度,它的對應的數值範圍是我們通過ofInt(),ofFloat()來指定的,這個返回值就表示當前時間所對應的數值的進度。
我們而我們上篇中在AnimatorUpdateListener()監聽的值是如何等到的呢?這裏我們得到的都是關於浮點型的小數,但是我們在打印的時候得到的卻是具體變化的數值,那麼又是如何得到具體的數值的呢?
其實我們在AnimatorUpdateListener()監聽中得到的數值值通過一個公式:

當前的值= 開始的值 + 當前的進度 * (結束的進度 - 開始的進度)

例如我們設置ValueAnimator.ofInt(100,400);動畫時間爲2s。當使用LinearInterpolator加速器(或是默認情況下)時間在1s時,顯示的進度爲0.5(這裏的顯示進度就是public float getInterpolation(float input)方法返回的參數)。那麼計算的公式就爲:

當前的值 = 100 + (400 - 100)* 0.5  

公式相當於我們做一個應用題:
小明從100的位置開始出發向400的位置開始跑去,在走到全程距離20%位置時,請問小明在哪個數字點上?

  • 自定義Interpolator插值器
    其實Android安卓提供的插值器已經非常全了。我個人感覺沒有必要再去自定義插值器。當然既然我們都清楚原理了,那麼就自定義一個自己的插值器試一下。 如下:
    public class MyInterploator implements TimeInterpolator {  
        @Override  
        public float getInterpolation(float input) {  
            return 1-input;  
        }  
    }  

自定義插值器注意2點:1.繼承TimeInterpolator 2.返回當前的顯示進度。這裏我們將進度反轉過來,當傳0的時候,我們讓它數值進度在完成的位置,當完成的時候,我們讓它在開始的位置。跟家複雜的可以參考自帶的其他插值器。
效果:


Evaluator估值器

TypeEvaluator估值器,他的作用是根據當期屬性的百分比來計算改變後的屬性值。Evaluator其實就是一個轉換器,他能把小數進度轉換成對應的數值位置。
先來看張圖(此圖來自Android自定義控件三部曲文章 )
image
這幅圖講述了從定義動畫的數字區間到通過AnimatorUpdateListener中得到當前動畫所對應數值的整個過程。下面我們對這四個步驟具體講解一下:
(1)、ofInt(0,400)表示指定動畫的數字區間,是從0運動到400;
(2)、加速器:上面我們講了,在動畫開始後,通過加速器會返回當前動畫進度所對應的數字進度,但這個數字進度是百分制的,以小數表示,如0.2
(3)、Evaluator:我們知道我們通過監聽器拿到的是當前動畫所對應的具體數值,而不是百分制的進度。那麼就必須有一個地方會根據當前的數字進度,將其轉化爲對應的數值,這個地方就是Evaluator;Evaluator就是將從加速器返回的數字進度轉成對應的數字值。所以上部分中,我們講到的公式:

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

(4)、監聽器:我們通過在AnimatorUpdateListener監聽器使用animation.getAnimatedValue()函數拿到Evaluator中返回的數字值。

在計算後就是得到ValueAnimator.addAnimatorUpdateListener()中變化的值。Android中已經爲我們提供了IntEvaluator(針對Int類型估值器),floatEvaluator(針對floatt類型估值器)和ArgbEvaluator(針對Color估值器)先不管ArgbEvaluator,IntEvaluator與floatEvaluator正好對應ValueAnimator.ofInt()與ValueAnimator.offloat(),如果你用.ofInt()那麼在計算時就用IntEvaluator計算。.offloat()就用floatEvaluator,之前我們沒有設置適因爲ofInt和ofFloat都是系統直接提供的函數,所以在使用時都會有默認的加速器和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繼承了我們的TypeEvaluator估值器。因爲是IntEvaluator所以指定的泛型類型爲Integer,同時裏面實現了一個方法,這個方法的參數的含義
- fraction 估值小數(就是加速器中的返回值,表示當前動畫的數值進度,百分制的小數表示。)
- startValue 動畫開始的值
- startValue 動畫結束的值
- 返回值: 就是我們在AnimatorUpdateListener監聽中得到具體運動的值。
我們可以看到返回值的運算公式正是我們在插值器中所提到的公式

當前的值= 開始的值 + 當前的進度 * (結束的進度 - 開始的進度)

我們用圖片中的例子如果加載器爲勻速加速器,動畫時長爲2s,在1s時(也就是動畫運動到一半的時候),這三個參數依次爲0.5,100,400,此時在監聽中得到的結果爲:

    當前的值 = 100 + (400 - 100)* 0.5  

結果爲250。

  • 自定義Evaluator估值器
    理解了估值器以後我們就可以試着自定義自己的估值器。只需要實現TypeEvaluator接口,並實現裏面唯一的一個方法即可。
    下面提供2種簡單的自定義估值器:
    1. 簡單實現MyEvalutor
      2gif.gif3gif.gif
·
       public class MyEvaluator implements TypeEvaluator<Integer> {  
        @Override  
        public Integer evaluate(float fraction, Integer startValue, Integer endValue) {  
            int startInt = startValue;  
            return (int)(200+startInt + fraction * (endValue - startInt));  
        }  
    }  

這個插值器在原來的運動的點的值基礎上加200。效果對比如下:

  1. 實現倒序輸出實例
    1gif.gif
·
    public class ReverseEvaluator implements TypeEvaluator<Integer> {  
    @Override  
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {  
        int startInt = startValue;  
        return (int) (endValue - fraction * (endValue - startInt));  
    }  
}

其中 fraction * (endValue - startInt)表示動畫實際運動的距離,我們用endValue減去實際運動的距離就表示隨着運動距離的增加,離終點越來越遠,這也就實現了從終點出發,最終運動到起點的效果了。
總結: 在加速器中,我們可以通過自定義加速器的返回的數值進度來改變返回數值的位置。比如上面我們實現的倒序動畫
在Evaluator中,我們又可以通過改變進度值所對應的具體數字來改變數值的位置。 所以可以通過重寫加速器改變數值進度來改變數值位置,也可以通過改變Evaluator中進度所對應的數值來改變數值位置。雖然2者都可以改變位置。但是插值器(加速器)是根據運動是時間來決定當前進度的百分比。估值器是根據加速器的百分比來計算具體的數值。作用相同,職責不同。
我個人理解加速器側重運動的狀態(彈動,忽快忽慢等)估值器就是計算的作用(可以改變運動開始結束的位置)。


ArgbEvalutor顏色估值器

從一開始我們就說過屬性動畫可以改變動畫的顏色。其實他就是通過ArgbEvalutor來做到的。爲什麼單獨把ArgbEvalutor拿出來呢?因爲與前兩者的使用稍微有些不同。使用如下:

`
                valueAnimator = ValueAnimator.ofInt(0xffff0000, 0xff0000ff);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int curValue = (int) animation.getAnimatedValue();
                        mTextView.setBackgroundColor(curValue);
                    }
                });
                 valueAnimator.setDuration(2000);
                 valueAnimator.setEvaluator(new ArgbEvaluator());
                 valueAnimator.start();

這裏我們使用8位16進制數值表示A,R,G,B的顏色值。使用ofInt()來創建對象。顏色變化爲由紅色(RED)變成藍色(BULE)效果圖如下:
4gif.gif
插值器基本原理,與自定義插值器差不多也就這樣了。 下面我們繼續往下看。


ValueAnimator.ofObject

之前在講到ValueAnimator我們提到了創建對象有.0fInt(),offloat()。但是他們只能傳入int與float類型的值。其實ValueAnimator還有一個方法就是ofObject()他可以傳入任意類型或是對象。他有什麼用呢?我們測試一下,看小面這個例子。
GIF.gif
在這裏我們動態的將字母A-Z。實現了動態變換的效果。那麼他是如何實現的呢?那我就先來了解下ofObject().

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

第一個參數是我們上面講到的估值器。第二個參數是我們要變換的值,和.0fInt(),offloat()一樣傳入的值越多,動畫效果變換越複雜。這裏第二個參數好理解。但是爲什麼要傳入估值器呢?其實這裏的估值器不是系統爲我們預設的之前將的三種估值器的任意一種,而是自定義估值器。因爲估值器的作用就是根據加速器返回的當前的進度來計算具體的值的。如果我們不自己定義,那系統是沒有辦法計算具體的值的,比如上面的例子計算A-Z變化某一進度具體的值,顯然我們講到的三種估值器都無法做到。所以我們需要自己定義自己的估值器。在明白了這一點使用起來就簡單多了。我們來看下代碼:

·  
        btn_object.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ValueAnimator valueAnimator = ValueAnimator.ofObject(new MyEvaluator(), 'A', 'Z');
                valueAnimator.setDuration(3000);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        char text = (char) animation.getAnimatedValue();
                        mTextView.setText(String.valueOf(text));
                    }
                });
                valueAnimator.start();
            }
        });

    /**
     * 計算A-Z 具體的值的自定義估值器 
     */
    public class MyEvaluator implements TypeEvaluator<Character> {
        @Override
        public Character evaluate(float fraction, Character startValue, Character endValue) {
            int startInt = (int) startValue;
            int endInt = (int) endValue;
            int curInt = (int) (startInt + fraction * (endInt - startInt));
            char result = (char) curInt;
            return result;
        }
    }

java基礎好的同學都應該知道 A-Z對應ASCII碼爲65-90 a-z爲97-122。所以我們在自定義估值器的時候就可以像上面那麼些。核心公式也是我們上面提到的,就不講解了。這段代碼也不難。這樣我們就可以實現文字變換的動畫。其實ofObject方法非常強大,不僅可以針對控件的某一屬性(如文字)。甚至可以作用與對象本身。下面我們在舉一個例子:
1GIF.gif
具體實現如下:


·
//Activity調用
btn_point.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                point.doPointAnim();
            }
        });

//自定義View
public class MyPointView extends View {
    private Point mCurPoint;
    public MyPointView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mCurPoint!=null){
            Paint mPaint=new Paint();//畫筆 用來畫圓
            mPaint.setColor(Color.RED);//畫筆顏色
            mPaint.setStyle(Paint.Style.FILL);//畫筆的格式 這裏用FILL畫出的圓就會填滿
            canvas.drawCircle(400f,600f,mCurPoint.getRadius(),mPaint);//400f,600f 表示基於屏幕圓心的X,Y軸座標 mCurPoint.getRadius()表示圓的半徑
        }

    }
    public void doPointAnim(){
        ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),new Point(20),new Point(200));
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurPoint = (Point)animation.getAnimatedValue();
                invalidate();//強制刷新 從而執行onDraw()方法
            }
        });
        animator.setDuration(1000);
        animator.setInterpolator(new BounceInterpolator());//插值器 彈起效果
        animator.start();
    }  

//圓的實體類
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;
    }
}

這樣我們就實現了傳入具體的對象。根據對象的變化做動畫。雖然我們沒有講到自定義View用到的也不難,裏面也沒有什麼難度就不細講了。


結語

這裏屬性動畫中的ValueAnimatot基本也就差不多了。其實學起來回頭看看也不是很難,在自定義View中重要是思路,平時還是已改多看看自定義View控件的文章,多學習多積累多練習才能熟練掌握,大家和我一起加油吧!

感謝

站在巨人的肩膀上可以讓我們看的更遠。
Android自定義控件三部曲文章

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