1. 程式人生 > >屬性動畫(property animation) &重複執行

屬性動畫(property animation) &重複執行

Android中的動畫分為檢視動畫(View Animation)即Tween Animation(補間動畫)、屬性動畫(Property Animation)以及Drawable動畫即Frame Animation(幀動畫)。從Android 3.0(API Level 11)開始,Android開始支援屬性動畫,本文主要講解如何使用屬性動畫。

檢視動畫和屬性動畫的優劣

檢視動畫侷限比較大,如下所述:
1.檢視動畫只能使用在View上面。
2.檢視動畫並沒有真正改變View相應的屬性值,這導致了UI效果與實際View狀態存在差異,並導致了一系列怪異行為,比如在使用了檢視動畫TranslateAnimation的View的UI上對其觸控,你可能驚訝地發現並沒有觸發觸控事件。
3.檢視動畫使用相當簡單,不過只能支援簡單的縮放、平移、旋轉、透明度基本的動畫,且有一定的侷限性。比如:你希望View有一個顏色的切換動畫;你希望可以使用3D旋轉動畫;你希望當動畫停止時,View的位置就是當前的位置;這些檢視動畫都無法做到。

鑑於檢視動畫以上缺陷,從Android 3.0引入了屬性動畫。屬性動畫具有以下特性:
1.屬性動畫應用面更廣,不僅僅應用於View,可以將其應用到任意的物件上面,且該物件不需要具有UI介面。
2.當將屬性動畫作用於某個物件時,可以通過呼叫物件的setXXX方法實際改變物件的值。所以,當將屬性動畫作用於某個View時,View物件對應的屬性值會被改變。

屬性動畫工作原理

其實屬性動畫的工作原理並不複雜,假設一個物件有一個屬性x,我們想通過屬性動畫動態更改該值,假設我們想在40ms內將x的值從0漸變到40,著時間的增長,對應的x值也相應地線性漸變,當動畫完成時,x的值就是我們設定的最終值40。

如果x值線性漸變,那麼x的變化速度就是勻速的。其實,我們也可以變速地改變x的值,這會我們可以一開始加速增加x的值,後面減速增加x的值,每種改變x值速度的方式都叫做時間插值器TimeInterpolator,勻速改變使用的時間插值器叫做LinearInterpolator,變速改變使用的時間插值器叫做AccelerateDecelerateInterpolator。動畫開始後,時間插值器會根據對應的演算法計算出某一時刻x的值,然後我們就可以用該計算出的值更新物件中x屬性的值,這就是屬性動畫的基本工作原理。

屬性動畫的主要類

這裡寫圖片描述

Animator

Animator是屬性動畫的基類,其是一個抽象類;

ViewPropertyAnimator

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

. setDuration(long duration)

通過setDuration方法可以設定動畫總共的持續時間,以毫秒為單位

// imageView1: 500 毫秒
imageView1.animate()  
        .translationX(500)
        .setDuration(500);

// imageView2: 2 秒
ObjectAnimator animator = ObjectAnimator.ofFloat(  
        imageView2, "translationX", 500);
animator.setDuration(2000);  
animator.start();  

.start()

通過start方法可以啟動動畫,動畫啟動後不一定會立即執行。如果之前通過呼叫setStartDelay方法設定了動畫延遲時間,那麼會在經過延遲時間之後再執行動畫;如果沒有設定過動畫的延遲時間,那麼動畫在呼叫了start()方法之後會立即執行。在呼叫了start()方法之後,動畫的isStarted()方法就會返回true;在動畫真正執行起來之後,動畫的isRunning()方法就會返回true,這時候動畫才會呼叫TimeInterpolator才開始工作計算屬性在某個時刻的值。呼叫動畫的start()方法所在的執行緒必須綁定了一個Looper物件,如果沒有繫結就會報錯。當然,UI執行緒(即主執行緒)早就預設綁定了一個Looper物件,所以在主執行緒中我們就無需擔心這個問題。如果我們想在一個View上使用屬性動畫,那麼我們應該保證我們是在UI執行緒上呼叫的動畫的start()方法。start()方法執行後會觸發動畫監聽器AnimatorListener的onAnimationStart方法的執行。

.setStartDelay(long startDelay)

可以通過呼叫setStartDelay方法設定動畫的延遲執行時間,比如呼叫setStartDelay(1000)意味著動畫在執行了start()方法1秒之後才真正執行,這種情況下,在呼叫了start()方法之後,isStarted()方法返回true,表示動畫已經啟動了,但是在start()方法呼叫後1s內,isRunning()方法返回false,表示動畫還未真正執行,比如在start()方法呼叫後第0.5秒,由於動畫還在延遲階段,所以isRunning()返回false;在start()方法執行1秒之後,isStarted()方法還是返回true,isRunning()方法也返回了true,表示動畫已經真正開始運行了。通過呼叫getStartDelay()方法可以返回我們設定的動畫延遲啟動時間,預設值是0。

setInterpolator(TimeInterpolator value)

我們可以通過呼叫setInterpolator方法改變動畫所使用的時間插值器,由於檢視動畫也需要使用時間插值器,所以我們可以使用android.view.animation名稱空間下的一系列插值器,將其與屬性動畫一起工作。通過動畫的getInterpolator方法可以獲取我們設定的時間插值器。

// imageView1: 線性 Interpolator,勻速
imageView1.animate()  
        .translationX(500)
        .setInterpolator(new LinearInterpolator());

// imageView: 帶施法前搖和回彈的 Interpolator
ObjectAnimator animator = ObjectAnimator.ofFloat(  
        imageView2, "translationX", 500);
animator.setInterpolator(new AnticipateOvershootInterpolator());  
animator.start();  

setTarget(Object target)

可以通過呼叫動畫的setTarget方法設定其要操作的物件,這樣可以更新該物件的某個屬性值。實際上,該方法對於ValueAnimator作用不大,因為ValueAnimator不是直接與某個物件打交道的。setTarget方法對於ObjectAnimator作用較大,因為ObjectAnimator需要繫結某個要操作的物件,下面會詳細介紹。

.pause()
Android中API Level 19在Animator中加入了pause()方法,該方法可以暫停動畫的執行。呼叫pause()方法的執行緒必須與呼叫start()方法的執行緒是同一個執行緒。如果動畫還沒有執行start()或者動畫已經結束了,那麼呼叫pause()方法沒有任何影響,直接被忽略。當執行了pause()方法後,動畫的isPaused()方法會返回true。pause()方法執行後會觸發動畫監聽器AnimatorPauseListener的onAnimationPause方法的執行。

.resume()
如果動畫通過呼叫pause()方法暫停了,那麼之後可以通過呼叫resume()方法讓動畫從上次暫停的地方繼續執行。resume()方法也是從API Level 19引入的,並且呼叫resume()方法的執行緒必須與呼叫start()方法的執行緒是同一個執行緒。如果動畫沒有處於暫停狀態(即isPaused()返回false),那麼呼叫resume()方法會被忽略。resume()方法執行後會觸發動畫監聽器AnimatorPauseListener的onAnimationResume方法的執行。

.end
end()方法執行後,動畫會結束執行,直接從當前狀態跳轉到最終的完成狀態,並將屬性值分配成動畫的終止值,並觸發動畫監聽器AnimatorListener的onAnimationEnd方法的執行。

.cancel()
cancel()方法執行後,動畫也會結束執行,但是與呼叫end方法不同的是,其不會將屬性值分配給動畫的終止值。比如一個動畫在400ms內將物件的x屬性從0漸變為40,當執行到第200ms時呼叫了cancel()方法,那麼屬性x的最終值是20,而不是40,這是與呼叫end()方法不同的,如果在第200ms呼叫了end()方法,那麼屬性x的最終值是40。呼叫cancel()方法後,會先觸發AnimatorListener的onAnimationCancel方法的執行,然後觸發onAnimationEnd方法的執行。

ValueAnimator

ValueAnimator繼承自抽象類Animator。要讓屬性動畫漸變式地更改物件中某個屬性的值,可分兩步操作:第一步,動畫需要計算出某一時刻屬性值應該是多少;第二步,需要將計算出的屬性值賦值給動畫的屬性。ValueAnimator只實現了第一步,也就是說ValueAnimator只負責以動畫的形式不斷計算不同時刻的屬性值,但需要我們開發者自己寫程式碼將計算出的值通過物件的setXXX等方法更新物件的屬性值。

ValueAnimator中有兩個比較重要的屬性,一個是TimeInterpolator型別的屬性,另一個是TypeEvaluator型別的屬性。TimeInterpolator指的就是時間插值器,在上面我們已經介紹過,在此不再贅述。TypeEvaluator是什麼呢?TypeEvaluator表示的是ValueAnimator對哪種型別的值進行動畫處理。ValueAnimator提供了四個靜態方法ofFloat()、ofInt()、ofArgb()和ofObject(),通過這四個方法可以對不同種類型的值進行動畫處理,這四個方法對應了四種TypeEvaluator,下面會詳細說明。

  1. ofFloat方法接收一系列的float型別的值,其內部使用了FloatEvaluator。通過該方法ValueAnimator可以對float值進行動畫漸變,其使用方法如下所示:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 500f);

    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float deltaY = (float)animation.getAnimatedValue();
            textView.setTranslationY(deltaY);
        }
    });

    //預設duration是300毫秒,我們設定為3000毫秒,也就是3秒
    valueAnimator.setDuration(3000);
    valueAnimator.start();

AnimatorUpdateListener有一個onAnimationUpdate方法,ValueAnimator會每隔一定時間(預設間隔10ms)計算屬性的值,每當計算的時候就會回撥onAnimationUpdate方法。在該方法中,我們通過呼叫ValueAnimator的getAnimatedValue()方法獲取到當前動畫計算出的屬性值,然後我們將該值傳入textView的setTranslationY()方法中,從而更新了textView的位置,這樣就通過ValueAnimator以動畫的形式移動textView。

2.ofInt方法與ofFloat方法很類似,只不過ofInt方法接收int型別的值,ofInt方法內部使用了IntEvaluator,其具體使用可參考上面ofFloat的使用程式碼,在此不再贅述。

3.從API Level 21(5.0版本)開始,ValueAnimator中加入了ofArgb方法,該方法接收一些列代表了顏色的int值,其內部使用了ArgbEvaluator,可以用該方法實現將一個顏色動畫漸變到另一個顏色,我們從中可以不斷獲取中間動畫產生的顏色值。

你可能納悶,既然傳入的還是int值,那直接用ofInt方法不就行了嗎,幹嘛還要新增一個ofArgb方法呢?實際上用ofInt方法是不能完成顏色動畫漸變的。我們知道一個int值包含四個位元組,在Android中第一個位元組代表Alpha分量,第二個位元組代表Red分量,第三個位元組代表Green分量,第四個位元組代表Blue分量,且我們常用16進製表示顏色,這樣看起來更明顯易懂一些,比如int值0xffff0000表示的紅色,0xff00ff00表示的是綠色,最前面的ff表示的是Alpha。ofArgb方法會通過ArgbEvaluator將顏色拆分成四個分量,然後分別對各個分量進行動畫計算,然後將四個計算完的分量再重新組合成一個表示顏色的int值,這就是ofArgb方法的工作原理。

 //ValueAnimator.ofArgb()方法是在API Level 21中才加入的
    if(Build.VERSION.SDK_INT >= 21){
        //起始顏色為紅色
        int startColor = 0xffff0000;
        //終止顏色為綠色
        int endColor = 0xff00ff00;
        ValueAnimator valueAnimator = ValueAnimator.ofArgb(startColor, endColor);
        valueAnimator.setDuration(3000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int color = (int)animation.getAnimatedValue();
                textView.setBackgroundColor(color);
            }
        });
        valueAnimator.start();
    }

4.ValueAnimator提供了一個ofObject方法,該方法接收一個TypeEvaluator型別的引數,我們需要實現該介面TypeEvaluator的evaluate方法,只要我們實現了TypeEvaluator介面,我們就能通過ofObject方法處理任意型別的資料。

我們之前提到ofArgb方法是從API Level 21才引入的,如果我們想在之前的這之前的版本中使用ofArgb的功能,怎麼辦呢?我們可以擴充套件TypeEvaluator,從而通過ofObject方法實現ofArgb方法的邏輯,如下所示:

 //起始顏色為紅色
    int startColor = 0xffff0000;
    //終止顏色為綠色
    int endColor = 0xff00ff00;
    ValueAnimator valueAnimator = ValueAnimator.ofObject(new TypeEvaluator() {
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            //從初始的int型別的顏色值中解析出Alpha、Red、Green、Blue四個分量
            int startInt = (Integer) startValue;
            int startA = (startInt >> 24) & 0xff;
            int startR = (startInt >> 16) & 0xff;
            int startG = (startInt >> 8) & 0xff;
            int startB = startInt & 0xff;

            //從終止的int型別的顏色值中解析出Alpha、Red、Green、Blue四個分量
            int endInt = (Integer) endValue;
            int endA = (endInt >> 24) & 0xff;
            int endR = (endInt >> 16) & 0xff;
            int endG = (endInt >> 8) & 0xff;
            int endB = endInt & 0xff;

            //分別對Alpha、Red、Green、Blue四個分量進行計算,
            //最終合成一個完整的int型的顏色值
            return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
                    (int)((startR + (int)(fraction * (endR - startR))) << 16) |
                    (int)((startG + (int)(fraction * (endG - startG))) << 8) |
                    (int)((startB + (int)(fraction * (endB - startB))));
        }
    }, startColor, endColor);
    valueAnimator.setDuration(3000);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int color = (int)animation.getAnimatedValue();
            textView.setBackgroundColor(color);
        }
    });
    valueAnimator.start();

ObjectAnimator

ObjectAnimator繼承自ValueAnimator。我們之前提到,要讓屬性動畫漸變式地更改物件中某個屬性的值,可分兩步操作:第一步,動畫需要計算出某一時刻屬性值應該是多少;第二步,需要將計算出的屬性值賦值給動畫的屬性。ValueAnimator只實現了第一步,也就是說ValueAnimator只負責以動畫的形式不斷計算不同時刻的屬性值,但需要我們開發者自己寫程式碼在動畫監聽器AnimatorUpdateListener的onAnimationUpdate方法中將計算出的值通過物件的setXXX等方法更新物件的屬性值。ObjectAnimator比ValueAnimator更進一步,其會自動呼叫物件的setXXX方法更新物件中的屬性值。

ObjectAnimator過載了ofFloat()、ofInt()、ofArgb()和ofObject()等靜態方法,我們下面以ofFloat為例說明:

 float value1 = 0f;
    float value2 = 500f;
    final ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView, "translationY", value1, value2);
    objectAnimator.setDuration(3000);
    objectAnimator.start();

在建構函式中,我們將textView作為target傳遞給ObjectAnimator,然後指定textView要變化的屬性是translationY,最後指定漸變範圍是從0到500。當動畫開始時,ObjectAnimator就會不斷呼叫textView的setTranslationY方法以更新其值。我們此處演示的是ObjectAnimator與View一起工作,其實ObjectAnimator可以與任意的Object物件工作。如果要更新某個物件中名為propery的屬性,那麼該Object物件必須具有一個setProperty的setter方法可以讓ObjectAnimator呼叫。

動畫效果改變的值

translationX和 translationY:這兩個屬性作為一種增量來控制著View物件水平或豎直移動。

rotation、rotationX和rotationY:這三個屬性控制View物件圍繞支點進行2D和3D旋轉

scaleX和scaleY. 這兩個屬性控制著View物件圍繞它的支點進行2D縮放。

pivotX和pivotY:這兩個屬性控制著view物件的支點位置,圍繞這個支點進行旋轉和縮放變換處理,預設情況下,該支點的位置就是View物件的中心點。

x和y這是兩個簡單實用的屬性,它描述了View物件在它的容器中的最終位置,它是最初的左上角座標和 translationX和 translationY值的累計和

alpha:它表示View物件的alpha透明度,預設值是1(不透明),0代表完全透明(不可見)

Android中提供的Interpolator(時間插值器)

AccelerateInterpolator      加速,開始時慢中間加速
DecelerateInterpolator       減速,開始時快然後減速
AccelerateDecelerateInterolator  先加速後減速,開始結束時慢,中間加速
AnticipateInterpolator       反向 ,先向相反方向改變一段再加速播放
AnticipateOvershootInterpolator  反向加超越,先向相反方向改變,再加速播放,會超出目的值然後緩 慢移動至目的值
BounceInterpolator        跳躍,快到目的值時值會跳躍,如目的值100,後面的值可能依次為85,77 ,70,80,90,100
CycleIinterpolator         迴圈,動畫迴圈一定次數,值的改變為一正弦函式:Math.sin(2 * mCycles * Math.PI * input)
LinearInterpolator         線性,線性均勻改變
OvershottInterpolator       超越,最後超出目的值然後緩慢改變到目的值

AnimatorSet

AnimatorSet繼承自Animator。AnimatorSet表示的是動畫的集合,我們可以通過AnimatorSet把多個動畫集合在一起,讓其序列或並行執行,從而創造出複雜的動畫效果。

我們想讓TextView先進行旋轉,然後進行平移,最後進行伸縮,我們可以通過AnimatorSet實現該效果,程式碼如下所示:

protected void animatorSet(Button bt2) {
        // TODO Auto-generated method stub
        //anim1實現TextView的旋轉動畫
        Animator anim1 = ObjectAnimator.ofFloat(bt2, "rotation", 0f, 360f);
        anim1.setDuration(2000);
        //anim2和anim3TextView的平移動畫
        Animator anim2 = ObjectAnimator.ofFloat(bt2, "translationX", 0f, 300f);
        anim2.setDuration(3000);
        Animator anim3 = ObjectAnimator.ofFloat(bt2, "translationY", 0f, 400f);
        anim3.setDuration(3000);
        //anim4實現TextView的伸縮動畫
        Animator anim4 = ObjectAnimator.ofFloat(bt2, "scaleX", 1f, 0.5f);
        anim4.setDuration(2000);


        //第一種方式
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playSequentially(anim1, anim2, anim4);
        animatorSet.playTogether(anim2, anim3);
        animatorSet.start();

        //第二種方式
        /*AnimatorSet anim23 = new AnimatorSet();
        anim23.playTogether(anim2, anim3);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playSequentially(anim1, anim23, anim4);
        animatorSet.start();*/

        //第三種方式
        /*AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.play(anim1).before(anim2);
        animatorSet.play(anim2).with(anim3);
        animatorSet.play(anim4).after(anim2);
        animatorSet.start();*/
    }

在第一種方式中,呼叫了animatorSet.playSequentially(anim1, anim2, anim4),該方法將anim1、anim2以及anim4按順序串聯起來放到了animatorSet中,這樣首先會讓動畫anim1執行,anim1執行完成後,會依次執行動畫anim2,執行完anim2之後會執行動畫anim3。通過呼叫animatorSet.playTogether(anim2, anim3),保證了anim2和anim3同時執行,即動畫anim1完成之後會同時執行anim2和anim3。

在第二種方式中,我們首先建立了一個AnimatorSet變數anim23,然後通過anim23.playTogether(anim2, anim3)將anim2和anim3組合成一個小的動畫集合。然後我們再把anim1、anim23以及anim4一起傳入到animatorSet.playSequentially(anim1, anim23, anim4)中,這樣anim1、anim23、anim4會依次執行,而anim23中的anim2和anim3會同時執行。該方式同時也演示了可以將一個AnimatorSet作為動畫的一部分放入另一個AnimatorSet中。

在第三種方式中,我們使用了AnimatorSet的play方法,該方法返回AnimatorSet.Builder型別,animatorSet.play(anim1).before(anim2)確保了anim1執行完了之後執行anim2,animatorSet.play(anim2).with(anim3)確保了anim2和anim3同時執行,animatorSet.play(anim4).after(anim2)確保了anim2執行完了之後執行anim4。需要說明的是animatorSet.play(anim1).before(anim2)與animatorSet.play(anim2).after(anim1)是完全等價的,之所以在上面程式碼中有的寫before,有的寫after,只是為了讓大家多瞭解一下API。

屬性動畫的重複執行

由於屬性動畫沒有重複執行的API,只能另闢蹊徑,下面程式碼是我的一種實現方式,希望有好的實現方式告知,大家一起提高。

    Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                runOnUiThread(new Runnable() {
                    public void run() {
                        showAnimation();
                    }
                });
            }
        };
        timer.schedule(task, 0, 900);

    private void showAnimation() {
        Animator anim1 = ObjectAnimator.ofFloat(img_load, "rotation", 360f, 0f);
        anim1.setDuration(1000);
        anim1.start();
    }