Android動畫機制完全解析

在Android 3.0之前的版本,我們能使用的動畫類型有兩種,分別是逐幀動畫和補間動畫;在Android3.0發佈時,Android SDK又爲開發者帶來了更加強大靈活的屬性動畫,使得實現複雜的動畫效果更加容易;隨着時間的推進,在Android4.4中,Android SDK又爲開發者帶來了android.transition框架,這使得開發者可以通過一種更直觀的方式定義動畫效果。咱們分別來介紹下這四種動畫。

1. 逐幀動畫(Frame Animation)

逐幀動畫也叫Drawable Animation,是最簡單最直觀的動畫類型,它利用人眼的視覺暫留效應—也就是光對視網膜所產生的視覺的光停止作用後,仍會保留一段時間的現象。

在Android中怎麼實現逐幀動畫呢?是由設計師給出一系列狀態不斷變化的圖片,開發者可以指定動畫中每一幀對應的圖片的持續時間,然後就可以播放動畫了。有兩種方式可以定義逐幀動畫,分別是採用XML資源文件和代碼實現。

XML資源文件方式
這是最常用的方式,我們將每一幀的圖片放到res/drawable-hdpi目錄中,然後在目錄中新建一個動畫XML文件,在這個文件中使用 animation_list標籤來定義動畫的幀序列,使用item標籤定義動畫的每一幀,並在其中指定幀的持續時間等屬性,格式如下:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/icon1" android:duration="150"/>
    <item android:drawable="@drawable/icon2" android:duration="150"/>
    <item android:drawable="@drawable/icon3" android:duration="150"/>
    <!--省略其他類似的定義-->
</animation-list>

其中android:oneshot用來控制動畫是否循環,true表示動畫不會循環播放,false表示會循環播放。android:duration用來指定每一幀的播放持續時間。

代碼方式
在代碼中定義逐幀動畫也很簡單,但不常用,語句如下:

    //代碼實現逐幀動畫
        AnimationDrawable animDrawable = new AnimationDrawable();
        for (int i = 1 ;i<4;i++){
            int id = getResources().getIdentifier("icon" + i, "drawable", getPackageName());
            Drawable drawable = getResources().getDrawable(id);
            animDrawable.addFrame(drawable,120);
        }
        imageView.setImageDrawable(animDrawable);
        animDrawable.setOneShot(false);

定義好逐幀動畫之後在某個條件中出發開始和停止的操作。僞代碼如下:

// 獲取AnimationDrawable對象實例,用來控制動畫的播放和停止
AnimationDrawable animDrawable = (AnimationDrawable) imageView.getDrawable();
// 開始動畫
animDrawable.start();
// 結束動畫
animDrawable.stop();

總結:

Frame Animation(逐幀動畫)相對來說比較簡單,但是在實際開發中使用的頻率還是比較高的。但是逐幀動畫只能實現比較小的動畫效果,如果複雜而且幀數比較多的動畫不太建議使用逐幀動畫,一方面是因爲會造成OOM,另一方面會顯得很卡,如果真是超級複雜的動畫的話建議選擇雙緩衝繪製View來實現。

2 補間動畫(Tween Animation)

補間動畫與逐幀動畫在本質上是不同的,逐幀動畫通過連續播放圖片來模擬動畫的效果,而補間動畫則是通過在兩個關鍵幀之間補充漸變的動畫效果來實現的。補間動畫的優點是可以節省空間。目前Android應用框架支持的補間動畫效果有以下5種。

AlphaAnimation:透明度(alpha)漸變效果,對應<alpha/>標籤。

TranslateAnimation:位移漸變,需要指定移動點的開始和結束座標,對應<translate/>標籤。

ScaleAnimation:縮放漸變,可以指定縮放的參考點,對應<scale/>標籤。

RotateAnimation:旋轉漸變,可以指定旋轉的參考點,對應<rotate/>標籤。

AnimationSet:組合漸變,支持組合多種漸變效果,對應<set/>標籤。

同樣,定義補間動畫也可以分爲XML資源文件和代碼兩種方式。不過在這之前還是先來看下插值器Interpolator。

插值器Interpolator
前面說到的Android系統會在補間動畫的開始和結束關鍵幀之間插入漸變值,它依據的便是Interpolator。具體來說,Interpolator會根據類型的不同,選擇不同的算法計算出在補間動畫期間所需要動態插入幀的密度和位置,Interpolator負責控制動畫的變化速度,使得前面所說的幾種動畫效果能夠以勻速、加速、減速、拋物線等多種速度進行變化。

在Android代碼中,Interpolator類其實就是一個空接口,它繼承自TimeInterpolator,TimeInterpolator時間插值器允許動畫進行非線性運動變換,如加速減速等,該接口中只有float getInterpolator(float input)這個方法。入參是一個0.0~1.0的值,返回值可以小於0.0也可以大於1.0,代碼如下:

public interface Interpolator extends TimeInterpolator{
}

public interface TimeInterpolator{
    float getInterpolation(float input);
}

Android SDK默認提供了幾個Interpolator的實現類:

  • AccelerateDecelerateInterpolator 在動畫開始與結束的地方速率改變比較慢,在中間的時候加速

  • AccelerateInterpolator 在動畫開始的地方速率改變比較慢,然後開始加速

  • AnticipateInterpolator 開始的時候向後然後向前甩

  • AnticipateOvershootInterpolator 開始的時候向後然後向前甩一定值後返回最後的值

  • BounceInterpolator 動畫結束的時候彈起

  • CycleInterpolator 動畫循環播放特定的次數,速率改變沿着正弦曲線

  • DecelerateInterpolator 在動畫開始的地方快然後慢

  • LinearInterpolator 以常量速率改變

  • OvershootInterpolator 向前甩一定值後再回到原來位置

如果android定義的interpolators不符合你的效果也可以自定義interpolators。

咱們接着說補間動畫的實現。

XML實現方式
這裏只實現一種,其他的都類似

<rotate xmlns:android="http://schemas.android.com/apk/res/android"  
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"  
    android:fromDegrees="0"  
    android:toDegrees="360"  
    android:duration="1000"  
    android:repeatCount="1"  
    android:repeatMode="reverse"/>  
<!--   
fromDegrees:表示旋轉的起始角度  
toDegrees:表示旋轉的結束角度  
repeatCount:旋轉的次數  默認值是0 代表旋轉1次  如果值是repeatCount=4 旋轉5次,值爲-1或者infinite時,表示補間動畫永不停止  
repeatMode 設置重複的模式。默認是restart。當repeatCount的值大於0或者爲infinite時纔有效。  
repeatCount=-1 或者infinite 循環了  
還可以設成reverse,表示偶數次顯示動畫時會做與動畫文件定義的方向相反的方向動行。  
 --> 

代碼實現

TranslateAnimation translateAnimation = new TranslateAnimation(0, 
200, 0, 0); translateAnimation.setDuration(2000); 
imageView.startAnimation(translateAnimation); 

在實際項目中,我們經常使用補間動畫,原因是補間動畫使用起來比較方便,功能也比逐幀動畫強大不少,而且還可以很方便地進行動畫疊加,實現更加複雜的效果。另外,在android系統中,所有與動畫相關的類都歸類在android.view.animation包之下,大家可以參考SDK文檔進行進一步學習。

至此,我們已經初步瞭解瞭如何在Android系統中使用各種動畫效果,包括逐幀動畫和補間動畫。顯而易見的是,在Android平臺之上,開發者們可以很方便地使用各種動畫效果來爲應用產品增色。

3 屬性動畫(Property Animation)

屬性動畫是在Android3.0中引入的,爲什麼要引入屬性動畫呢?

在補間動畫中,我們只能改變View的繪製效果,View的真實屬性沒有變化,而屬性動畫則是直接改變View對象的屬性值。什麼意思?假如你把一個Button從屏幕的左上角移到右下角,你在補間動畫中點擊右下角的Button按鈕是沒有任何作用的,因爲Button的真實屬性位置還在左上角。這是補間動畫的致命缺陷。

然後補間動畫還有一個缺陷,就是它只能夠實現移動、縮放、旋轉和淡入淡出這四種動畫操作,那如果我們希望可以對View的背景色進行動態地改變呢?很遺憾,我們只能靠自己去實現了。說白了,之前的補間動畫機制就是使用硬編碼的方式來完成的,功能限定死就是這些,基本上沒有任何擴展性可言。

屬性動畫幾乎可以對任何對象執行動畫,而不是侷限在View對象上。補間動畫是隻能夠作用在View上,只要是任意繼承View的控件都可以使用,但是對於非View控件,補間動畫就愛莫能助了。可能有的朋友會感到不能理解,我怎麼會需要對一個非View的對象進行動畫操作呢?這裏我舉一個簡單的例子,比如說我們有一個自定義的View,在這個View當中有一個Point對象用於管理座標,然後在onDraw()方法當中就是根據這個Point對象的座標值來進行繪製的。也就是說,如果我們可以對Point對象進行動畫操作,那麼整個自定義View的動畫效果就有了。

說了那麼多屬性動畫有而補間動畫不足的地方,相信大家已經對飢渴難耐了,下面就開始學習屬性動畫。

Evaluator
在說屬性動畫之前,還是要聊一聊Evaluator這個概念。它是用來控制屬性動畫如何計算屬性值的。它的接口定義是TypeEvaluator,其中定義了evaluate方法,供不同的子類實現。可能在大多數情況下我們使用屬性動畫的時候都不會用到TypeEvaluator,但是大家還是應該瞭解一下它的用法,以防止當我們遇到一些解決不掉的問題時能夠想起來還有這樣的一種解決方案。

那麼TypeEvaluator的作用到底是什麼呢?簡單來說,就是告訴動畫系統如何從初始值過度到結束值。我們在上一篇文章中學到的ValueAnimator.ofFloat()方法就是實現了初始值與結束值之間的平滑過度,那麼這個平滑過度是怎麼做到的呢?其實就是系統內置了一個FloatEvaluator,它通過計算告知動畫系統如何從初始值過度到結束值,我們來看一下FloatEvaluator的代碼實現:

    public class FloatEvaluator implements TypeEvaluator {  
        public Object evaluate(float fraction, Object startValue, Object endValue) {  
            float startFloat = ((Number) startValue).floatValue();  
            return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);  
        }  
    }  

可以看到,FloatEvaluator實現了TypeEvaluator接口,然後重寫evaluate()方法。evaluate()方法當中傳入了三個參數,第一個參數fraction非常重要,這個參數用於表示動畫的完成度的,我們應該根據它來計算當前動畫的值應該是多少,第二第三個參數分別表示動畫的初始值和結束值。那麼上述代碼的邏輯就比較清晰了,用結束值減去初始值,算出它們之間的差值,然後乘以fraction這個係數,再加上初始值,那麼就得到當前動畫的值了。

好的,那FloatEvaluator是系統內置好的功能,並不需要我們自己去編寫,但介紹它的實現方法是要爲我們後面的功能鋪路的。前面我們使用過了ValueAnimator的ofFloat()和ofInt()方法,分別用於對浮點型和整型的數據進行動畫操作的,但實際上ValueAnimator中還有一個ofObject()方法,是用於對任意對象進行動畫操作的。但是相比於浮點型或整型數據,對象的動畫操作明顯要更復雜一些,因爲系統將完全無法知道如何從初始對象過度到結束對象,因此這個時候我們就需要實現一個自己的TypeEvaluator來告知系統如何進行過度。

ValueAnimator

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

但是ValueAnimator的用法卻一點都不復雜,我們先從最簡單的功能看起吧,比如說想要將一個值從0平滑過渡到1,時長300毫秒,就可以這樣寫:

    ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);  
    anim.setDuration(300);  
    anim.start();  

怎麼樣?很簡單吧,調用ValueAnimator的ofFloat()方法就可以構建出一個ValueAnimator的實例,ofFloat()方法當中允許傳入多個float類型的參數,這裏傳入0和1就表示將值從0平滑過渡到1,然後調用ValueAnimator的setDuration()方法來設置動畫運行的時長,最後調用start()方法啓動動畫。

另外ofFloat()方法當中是可以傳入任意多個參數的,因此我們還可以構建出更加複雜的動畫邏輯,比如說將一個值在5秒內從0過渡到5,再過渡到3,再過渡到10,就可以這樣寫:

    ValueAnimator anim = ValueAnimator.ofFloat(0f, 5f, 3f, 10f);  
    anim.setDuration(5000);  
    anim.start();  

當然也許你並不需要小數位數的動畫過渡,可能你只是希望將一個整數值從0平滑地過渡到100,那麼也很簡單,只需要調用ValueAnimator的ofInt()方法就可以了,如下所示:

ValueAnimator anim = ValueAnimator.ofInt(0, 100); 

那麼除此之外,我們還可以調用setStartDelay()方法來設置動畫延遲播放的時間,調用setRepeatCount()和setRepeatMode()方法來設置動畫循環播放的次數以及循環播放的模式,循環模式包括RESTART和REVERSE兩種,分別表示重新播放和倒序播放的意思。這些方法都很簡單,我就不再進行詳細講解了。

ObjectAnimator

相比於ValueAnimator,ObjectAnimator可能纔是我們最常接觸到的類,因爲ValueAnimator只不過是對值進行了一個平滑的動畫過渡,但我們實際使用到這種功能的場景好像並不多。而ObjectAnimator則就不同了,它是可以直接對任意對象的任意屬性進行動畫操作的,比如說View的alpha屬性。

不過雖說ObjectAnimator會更加常用一些,但是它其實是繼承自ValueAnimator的,底層的動畫實現機制也是基於ValueAnimator來完成的,因此ValueAnimator仍然是整個屬性動畫當中最核心的一個類。那麼既然是繼承關係,說明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,它們的用法也非常類似,這裏如果我們想要將一個TextView在5秒中內從常規變換成全透明,再從全透明變換成常規,就可以這樣寫:

    ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
    animator.setDuration(5000);  
    animator.start();  

可以看到,我們還是調用了ofFloat()方法來去創建一個ObjectAnimator的實例,只不過ofFloat()方法當中接收的參數有點變化了。這裏第一個參數要求傳入一個object對象,我們想要對哪個對象進行動畫操作就傳入什麼,這裏我傳入了一個textview。第二個參數是想要對該對象的哪個屬性進行動畫操作,由於我們想要改變TextView的不透明度,因此這裏傳入”alpha”。後面的參數就是不固定長度了,想要完成什麼樣的動畫就傳入什麼值,這裏傳入的值就表示將TextView從常規變換成全透明,再從全透明變換成常規。之後調用setDuration()方法來設置動畫的時長,然後調用start()方法啓動動畫。效果就不展示了,大家可以自行測試。

組合動畫(AnimatorSet)

獨立的動畫能夠實現的視覺效果畢竟是相當有限的,因此將多個動畫組合到一起播放就顯得尤爲重要。幸運的是,Android團隊在設計屬性動畫的時候也充分考慮到了組合動畫的功能,因此提供了一套非常豐富的API來讓我們將多個動畫組合到一起。

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

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

好的,有了這四個方法,我們就可以完成組合動畫的邏輯了,那麼比如說我們想要讓TextView先從屏幕外移動進屏幕,然後開始旋轉360度,旋轉的同時進行淡入淡出操作,就可以這樣寫:

    ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);  
    ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);  
    ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
    AnimatorSet animSet = new AnimatorSet();  
    animSet.play(rotate).with(fadeInOut).after(moveIn);  
    animSet.setDuration(5000);  
    animSet.start();  

可以看到,這裏我們先是把三個動畫的對象全部創建出來,然後new出一個AnimatorSet對象之後將這三個動畫對象進行播放排序,讓旋轉和淡入淡出動畫同時進行,並把它們插入到了平移動畫的後面,最後是設置動畫時長以及啓動動畫。

Animator監聽器

在很多時候,我們希望可以監聽到動畫的各種事件,比如動畫何時開始,何時結束,然後在開始或者結束的時候去執行一些邏輯處理。這個功能是完全可以實現的,Animator類當中提供了一個addListener()方法,這個方法接收一個AnimatorListener,我們只需要去實現這個AnimatorListener就可以監聽動畫的各種事件了。

大家已經知道,ObjectAnimator是繼承自ValueAnimator的,而ValueAnimator又是繼承自Animator的,因此不管是ValueAnimator還是ObjectAnimator都是可以使用addListener()這個方法的。另外AnimatorSet也是繼承自Animator的,因此addListener()這個方法算是個通用的方法。

添加一個監聽器的代碼如下所示:

anim.addListener(new AnimatorListener() {  
    @Override  
    public void onAnimationStart(Animator animation) {  
    }  

    @Override  
    public void onAnimationRepeat(Animator animation) {  
    }  

    @Override  
    public void onAnimationEnd(Animator animation) {  
    }  

    @Override  
    public void onAnimationCancel(Animator animation) {  
    }  
}); 

可以看到,我們需要實現接口中的四個方法,onAnimationStart()方法會在動畫開始的時候調用,onAnimationRepeat()方法會在動畫重複執行的時候調用,onAnimationEnd()方法會在動畫結束的時候調用,onAnimationCancel()方法會在動畫被取消的時候調用。

但是也許很多時候我們並不想要監聽那麼多個事件,可能我只想要監聽動畫結束這一個事件,那麼每次都要將四個接口全部實現一遍就顯得非常繁瑣。沒關係,爲此Android提供了一個適配器類,叫作AnimatorListenerAdapter,使用這個類就可以解決掉實現接口繁瑣的問題了,如下所示:

    anim.addListener(new AnimatorListenerAdapter() {  
    });  

這裏我們向addListener()方法中傳入這個適配器對象,由於AnimatorListenerAdapter中已經將每個接口都實現好了,所以這裏不用實現任何一個方法也不會報錯。那麼如果我想監聽動畫結束這個事件,就只需要單獨重寫這一個方法就可以了,如下所示:

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

使用XML編寫動畫

我們可以使用代碼來編寫所有的動畫功能,這也是最常用的一種做法。不過,過去的補間動畫除了使用代碼編寫之外也是可以使用XML編寫的,因此屬性動畫也提供了這一功能,即通過XML來完成和代碼一樣的屬性動畫功能。

通過XML來編寫動畫可能會比通過代碼來編寫動畫要慢一些,但是在重用方面將會變得非常輕鬆,比如某個將通用的動畫編寫到XML裏面,我們就可以在各個界面當中輕鬆去重用它。

如果想要使用XML來編寫動畫,首先要在res目錄下面新建一個animator文件夾,所有屬性動畫的XML文件都應該存放在這個文件夾當中。然後在XML文件中我們一共可以使用如下三種標籤:

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

那麼比如說我們想要實現一個從0到100平滑過渡的動畫,在XML當中就可以這樣寫:

    <animator xmlns:android="http://schemas.android.com/apk/res/android"  
        android:valueFrom="0"  
        android:valueTo="100"  
        android:valueType="intType"/>  

而如果我們想將一個視圖的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"/> 

我們也可以使用XML來完成複雜的組合動畫操作,比如將一個視圖先從屏幕外移動進屏幕,然後開始旋轉360度,旋轉的同時進行淡入淡出操作,就可以這樣寫:

    <set xmlns:android="http://schemas.android.com/apk/res/android"  
        android:ordering="sequentially" >  

        <objectAnimator  
            android:duration="2000"  
            android:propertyName="translationX"  
            android:valueFrom="-500"  
            android:valueTo="0"  
            android:valueType="floatType" >  
        </objectAnimator>  

        <set android:ordering="together" >  
            <objectAnimator  
                android:duration="3000"  
                android:propertyName="rotation"  
                android:valueFrom="0"  
                android:valueTo="360"  
                android:valueType="floatType" >  
            </objectAnimator>  

            <set android:ordering="sequentially" >  
                <objectAnimator  
                    android:duration="1500"  
                    android:propertyName="alpha"  
                    android:valueFrom="1"  
                    android:valueTo="0"  
                    android:valueType="floatType" >  
                </objectAnimator>  
                <objectAnimator  
                    android:duration="1500"  
                    android:propertyName="alpha"  
                    android:valueFrom="0"  
                    android:valueTo="1"  
                    android:valueType="floatType" >  
                </objectAnimator>  
            </set>  
        </set>  

    </set>  

這段XML實現的效果和我們剛纔通過代碼來實現的組合動畫的效果是一模一樣的,每個參數的含義都非常清楚,相信大家都是一看就懂,我就不再一一解釋了。

最後XML文件是編寫好了,那麼我們如何在代碼中把文件加載進來並將動畫啓動呢?只需調用如下代碼即可:

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

調用AnimatorInflater的loadAnimator來將XML動畫文件加載進來,然後再調用setTarget()方法將這個動畫設置到某一個對象上面,最後再調用start()方法啓動動畫就可以了,就是這麼簡單。

過渡動畫(Transition Animation)

過渡動畫是在Android4.4引入的新的動畫框架,它的本質上還是屬性動畫,只不過是對屬性動畫做了一層封裝,方便開發者實現Activity或者View的過渡動畫效果。

和屬性動畫相比,過渡動畫最大的不同是需要爲動畫前後準備不同的佈局,並通過對應的API實現兩個佈局的過渡動畫,而屬性動畫只需要一個佈局文件。

在使用Transition Animation框架實現動畫效果之前,我們先來了解這個框架的幾個基本概念。

  • Scene:定義了頁面的當前狀態信息,Scene的實例化一般通過靜態工廠方法實現。
  • Transition:定義了界面之間切換的動畫信息,在使用TransitionManager時沒有指定使用哪個Transition,那麼會使用默認的AutoTransition。由源碼可以看出AutoTransition的動畫效果就是先隱藏對象變透明,然後移動指定對象,最後顯示出來。
  • TransitionManager:控制Scene之間切換的控制器,切換常用的方法有以下兩個,其中sDefaultTransition就是前面說的AutoTransition的實例。

由於篇幅問題具體實現代碼就不在給了,有什麼問題歡迎留言。

參考:
《Android高級進階》
Android屬性動畫完全解析(上),初識屬性動畫的基本用法

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