天天看點

Android屬性動畫完全解析(一)

1.引言

早期Android提供了兩種動畫方式,逐幀動畫(frame-by-frame animation)和補間動畫(tweened animation)。逐幀動畫的工作原理如同卡通片一樣,通過一張張單獨的圖檔連貫起來播放。補間動畫則是可以對View進行一系列的動畫操作,包括淡入淡出、縮放、平移、旋轉四種。

這裡就會有人有疑問了,然而我們還是要看到這兩種動畫的一些限制,猶其是補間動畫這種非常常用的動畫,在功能和可擴充方面還是有相當大的局限性,主要展現在以下三個方面:

  1. 補間動畫是隻能夠作用在View上的,無法非View的對象進行動畫操作。這是什麼意思呢?舉個例子,比如說我們有一個自定義的View,在這個View當中有一個Point對象用于管理坐标,然後在onDraw()方法當中就是根據這個Point對象的坐标值來進行繪制的。也就是說,如果我們可以對Point對象進行動畫操作,那麼整個自定義View的動畫效果就有了。顯然,補間動畫是不具備這個功能的,這是它的第一個缺陷。
  2. 補間動畫機制就是使用寫死的方式來完成的,功能上無法擴充。比如想讓view的背景色變換,它是沒辦法做到的
  3. 補間動畫還有一個緻命的缺陷,就是它隻是改變了View的顯示效果而已,而不會真正去改變View的屬性。什麼意思呢?比如說,現在螢幕的左上角有一個按鈕,然後我們通過補間動畫将它移動到了螢幕的右下角,現在你可以去嘗試點選一下這個按鈕,點選事件是絕對不會觸發的,因為實際上這個按鈕還是停留在螢幕的左上角,隻不過補間動畫将這個按鈕繪制到了螢幕的右下角而已。

為了解決上述的問題,Android團隊推出了新的動畫機制:Property Animation,也就是屬性動畫。

二、ValueAnimator

在我們的動畫機制中,有兩個類是比較基本的,一個是ValueAnimator,一個是ObjectAnimator,前面一個是基類,後一個是子類,但使用更為友善,也很常用。

ValueAnimator是整個屬性動畫機制當中最核心的一個類,前面我們已經提到了,屬性動畫的運作機制是通過不斷地對值進行操作來實作的,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的。它的内部使用一種時間循環的機制來計算值與值之間的動畫過渡,我們隻需要将初始值和結束值提供給ValueAnimator,并且告訴它動畫所需運作的時長,那麼ValueAnimator就會自動幫我們完成從初始值平滑地過渡到結束值這樣的效果。除此之外,ValueAnimator還負責管理動畫的播放次數、播放模式、以及對動畫設定監聽器等,确實是一個非常重要的類。

注意,ValueAnimator操作的隻是值的變化,我們可以為看下面這個例子:

ValueAnimator anim = ValueAnimator.ofFloat(f, f);  
anim.setDuration();  
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
    @Override  
    public void onAnimationUpdate(ValueAnimator animation) {  
        float currentValue = (float) animation.getAnimatedValue();  
        Log.d("TAG", "cuurent value is " + currentValue);  
    }  
});  
anim.start(); 
           

這個裡面我們進行的操作主要是将數值0到1進行變化,變化的時間為300毫秒,而且設定了一個updateListener,來監聽并列印數值的變化。可以料到,程式運作的結果就是在300毫秒内,列印了一串0-1之間的數。

但是說的是屬性動作,隻模拟了數值的變化怎麼行。是的,ValueAnimator隻是将數值進行操作,本身并沒有涉及到屬性。在使用中需要配合屬性來進行。

它的好處在于:不必針對某個單一的屬性,你可以自己根據目前動畫的計算值,來操作任何屬性,隻要這個屬性有getter,setter,比如【希望一個動畫能夠讓View既可以放大、背景又能夠由淺變深(3個屬性scaleX,scaleY,alpha)】,就可以利用上述構造出來的anim,直接設定:

//利用單個ValueAnimator,對多個屬性進行操作
View.setScalX(anim);
View.setScalY(anim);
View.setAlpha(anim);
           

這裡就不得不扯一下ObjectAnimator的一個非典型用法:

public void rotateyAnimRun(final View view)  
{  
    ObjectAnimator anim = ObjectAnimator//  
            .ofFloat(view, "suibian", F,  F)//  
            .setDuration();//  
    anim.start();  
    anim.addUpdateListener(new AnimatorUpdateListener()  
    {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation)  
        {  
            float cVal = (Float) animation.getAnimatedValue();  
            view.setAlpha(cVal);  
            view.setScaleX(cVal);  
            view.setScaleY(cVal);  
        }  
    });  
}  
           

這一串代碼用了ObjectAnimator,實作的功能和前面用ValueAnimator一模一樣,為啥呢?主要看這一句。

ObjectAnimator anim = ObjectAnimator//

.ofFloat(view, “suibian”, 0.0F, 1.0F)//

.setDuration(500);

解釋一樣,view就是我們要操作的對象,第二個參數是屬性,看到我們傳了一個”suibian ”進去,“suibian”并不是這個對象有的屬性,于是ObjectAnimator就退化為ValueAnimator對第三個第四個參數進行值操作,使用是就需要利用屬性的setter方法了。

既然說到了操作多個動畫,這裡再提另外一種方法:使用propertyValuesHolder,聽名字就可以猜到用法

public void propertyValuesHolder(View view)  
    {  
        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", ,  
                , );  
        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", ,  
                , );  
        PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", ,  
                , );  
        ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY,pvhZ).setDuration().start();  
    }  
           

三、ObjectAnimator

談起ObjectAnimator,還是要對比ValueAnimator.ObjectAnimator就直覺的特點就是可以直接對任意對象的任意屬性進行動畫操作的,比如說View的alpha屬性。

但說到繼承關系,ObjectAnimator歸根結底調用的都是ValueAnimator,是以呢,其實ObjectAnimator能夠實作的用ValueAnimator照樣能夠實作。

由于參數比較直覺,我直接用一個例子來展示了:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "scaleX", f, f,f);  
animator.setDuration();  
animator.start();  
           

可以看出,我們對一個textView在X軸方向上進行縮放操作,與前面的不同,這裡我們傳了了三個參數,1.0f—>0.5f—>1.0f,對應的效果就是textView在x方向上先縮小到一半,再放大到原來大小。

四、組合動畫

到這裡,有讀者可能想問,如果有多個動畫,而且動畫的播放有一個先後順序怎麼辦。嗯,Android團隊肯定也想到這個問題了。

實作組合動畫功能主要需要借助AnimatorSet這個類,這個類提供了一個play()方法,如果我們向這個方法中傳入一個Animator對象(ValueAnimator或ObjectAnimator)将會傳回一個AnimatorSet.Builder的執行個體,AnimatorSet.Builder中包括以下四個方法:

after(Animator anim)   将現有動畫插入到傳入的動畫之後執行
after(long delay)   将現有動畫延遲指定毫秒後執行
before(Animator anim)   将現有動畫插入到傳入的動畫之前執行
with(Animator anim)   将現有動畫和傳入的動畫同時執行
           

直接上代碼:

public void playWithAfter(View view)  
    {  
        float cx = mBlueBall.getX();  

        ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX",  
                f, f);  
        ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY",  
                f, f);  
        ObjectAnimator anim3 = ObjectAnimator.ofFloat(mBlueBall,  
                "x",  cx ,  f);  
        ObjectAnimator anim4 = ObjectAnimator.ofFloat(mBlueBall,  
                "x", cx);  

        /** 
         * anim1,anim2,anim3同時執行 
         * anim4接着執行 
         */  
        AnimatorSet animSet = new AnimatorSet();  
        animSet.play(anim1).with(anim2);  
        animSet.play(anim2).with(anim3);  
        animSet.play(anim4).after(anim3);  
        animSet.setDuration();  
        animSet.start();  
    }  
}  
           

那一串的playwith,playafter就是執行的先後。雖然是支援鍊式程式設計的,但是還是要謹慎的,不要擠在一起寫,一句就寫完,分開來寫比較好。

最後提一下監聽器,addListener裡面總共實作了四個接口,onAnimationStart()方法會在動畫開始的時候調用,onAnimationRepeat()方法會在動畫重複執行的時候調用,onAnimationEnd()方法會在動畫結束的時候調用,onAnimationCancel()方法會在動畫被取消的時候調用。

有人會覺得挺麻煩的,沒關系,Android提供了一個擴充卡類,叫作AnimatorListenerAdapter,使用這個類就可以解決掉實作接口繁瑣的問題了,可以實作自己需要的接口:

anim.addListener(new AnimatorListenerAdapter() {  
    @Override  
    public void onAnimationEnd(Animator animation) {  
    }  
});  
           

在xml中實作動畫的方式也是極其簡單的,我們可以在res目錄下面建立一個animator檔案夾,所有屬性動畫的XML檔案都存放在這個檔案夾當中。

XML檔案中主要有三種标簽:

<animator>  對應代碼中的ValueAnimator
<objectAnimator>  對應代碼中的ObjectAnimator
<set>  對應代碼中的AnimatorSet
           

而如果我們想将一個視圖的alpha屬性從1變成0,就可以這樣寫:

<objectAnimator     xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="1"  
    android:valueTo="0"  
    android:valueType="floatType"  
    android:propertyName="alpha"/>  
           

然後怎麼在代碼中調起這段動畫呢,一句話搞定

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);  
animator.setTarget(view);  
animator.start();
           

loadAnimator進來,和使用layout很類似有木有,就是這麼簡單。

有興趣的可以去看看鴻洋用動畫實作的線:

http://blog.csdn.net/lmj623565791/article/details/38067475