本篇文章部分參考自郭神部落格 http://blog.csdn.net/guolin_blog/article/details/43536355#t0
上一篇主要介紹了補間動畫,但是補間動畫有很大的局限性,比如隻能對View進行操作,隻能實作移動,縮放,旋轉,淡入淡出效果,一旦超出這種需求,補間動畫就顯得捉襟見肘了。最重要的是補間動畫隻是改變了View的顯示效果,而不會改變View的屬性。比如在螢幕的左邊放一個Button,并設定了點選事件,然後通過平移補間動畫移動到右邊,點選事件的響應仍然在左邊。因為實際上這個按鈕還是在螢幕的左邊,補間動畫隻是把這個Button繪制到了右邊。
因為補間動畫的這些局限性,android在3.0版本(API 11)的時候,引進了一種全新的動畫:屬性動畫。
屬性動畫的優勢有哪些?
- 屬性動畫可以作用到所有的Java對象,不再局限于View對象。
- 可以實作更多動畫效果,不隻限于平移,翻轉,縮放,淡入淡出四種。
- 屬性動畫實作的不再隻是視覺效果,對象的屬性會随着動畫的改變而改變。
屬性動畫的原理
在設定的時間間隔内,通過不斷對該對象值進行改變,并指派給對象的屬性,進而實作該對象的動畫效果。
屬性動畫的使用
ValueAnimator類
ValueAnimator類可以說是屬性動畫中最核心的類了,上面我們說了,屬性動畫的運作機制是通過不斷地對值進行操作來實作的,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的。ValueAnimator還負責管理動畫的播放次數,播放模式,以及對動畫設定監聽器等,ValueAnimator類當中有三個重要的方法:
ValueAnimator.ofInt(int values) // 将初始值 以整型數值的形式 過渡到結束值
ValueAnimator.ofFloat(float values)// 将初始值 以浮點型數值的形式 過渡到結束值
ValueAnimator.ofObject(int values) // 将初始值 以對象的形式 過渡到結束值
我們以 ValueAnimator.ofFloat(float values) 為例,
ValueAnimator anim = ValueAnimator.ofFloat(f, f);
anim.setDuration(); //設定動畫持續時間,不設定預設值為300ms
anim.setStartDelay(); // 設定動畫延遲播放時間 機關為 ms
anim.setRepeatCount(); // 設定動畫重複播放次數 = 重放次數+1
// 動畫播放次數 = infinite時,動畫無限重複
anim.setRepeatMode(ValueAnimator.RESTART); // 設定重複播放動畫模式
// ValueAnimator.RESTART(預設):正序重放
// ValueAnimator.REVERSE:倒序回放
anim.start();
調用ValueAnimator的ofFloat()方法就可以建構出一個ValueAnimation的執行個體,ofFloat()方法當中允許傳入多個float值,這裡傳入和1 就表示将值從0平滑過渡到1,最後調用start()方法啟動動畫。
這隻是一個将值從0過渡到1的動畫,看不到任何界面效果,如果想知道動畫是不是已經運作了,這就需要借助監聽器實作,如下所示:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
...
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();
可以看到,這裡我們通過addUpdateListener()方法來添加一個動畫的監聽器,在動畫執行的過程中會不斷地進行回調,我們隻需要在回調方法當中将目前的值取出并列印出來,就可以知道動畫有沒有真正運作了。運作上述代碼,控制台列印如下所示:
從列印日志的值我們就可以看出,ValueAnimator确實已經在正常工作了,值在300毫秒的時間内從0平滑過渡到了1,而這個計算工作就是由ValueAnimator幫助我們完成的。另外ofFloat()方法當中是可以傳入任意多個參數的,是以我們還可以建構出更加複雜的動畫邏輯,比如說将一個值在5秒内從0過渡到5,再過渡到3,再過渡到10,就可以這樣寫:
ValueAnimator anim = ValueAnimator.ofFloat(, , , );
anim.setDuration();
anim.start();
當然也許你并不需要小數位數的動畫過渡,可能你隻是希望将一個整數值從0平滑地過渡到100,那麼也很簡單,隻需要調用ValueAnimator的ofInt()方法就可以了,如下所示:
ObjectAnimator類
相比于ValueAnimator,ObjectAnimator可能才是我們最常接觸到的類,因為ValueAnimator隻不過是對值進行了一個平滑的動畫過渡,但我們實際使用到這種功能的場景好像并不多。而ObjectAnimator則就不同了,它是可以直接對任意對象的任意屬性進行動畫操作的,比如說View的alpha屬性。
ObjectAnimator類是繼承自ValueAnimator的,底層的動畫實作機制也是基于ValueAnimator來完成的,是以ValueAnmiator仍然是屬性動畫中最核心的類。既然是繼承關系,ValueAnimator中的方法在ObjectAnimator中也是可以正常使用的,用法也非常相似,這裡如果我們想要将一個TextView在5秒中内從正常變換成全透明,再從全透明變換成正常,就可以這樣寫:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", f, f, f);
animator.setDuration();
animator.start();
這裡第一個參數要求傳入一個object對象,我們想要對哪個對象進行動畫操作就傳入什麼,這裡我傳入了一個textview。第二個參數是想要對該對象的哪個屬性進行動畫操作,由于我們想要改變TextView的不透明度,是以這裡傳入”alpha”。後面的參數就是不固定長度了,想要完成什麼樣的動畫就傳入什麼值,這裡傳入的值就表示将TextView從正常變換成全透明,再從全透明變換成正常。之後調用setDuration()方法來設定動畫的時長,然後調用start()方法啟動動畫。
學會了這一個用法之後,其它的用法我們就可以舉一反三了,那比如說我們想要将TextView進行一次360度的旋轉,就可以這樣寫:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "rotation", f, f);
animator.setDuration();
animator.start();
可以看到,這裡我們将第二個參數改成了”rotation”,然後将動畫的初始值和結束值分别設定成0和360,現在運作一下代碼,效果如下圖所示:
那麼如果想要将TextView先向左移出螢幕,然後再移動回來,就可以這樣寫:
float curTranslationX = textview.getTranslationX();
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "translationX", curTranslationX, -f, curTranslationX);
animator.setDuration();
animator.start();
這裡我們先是調用了TextView的getTranslationX()方法來擷取到目前TextView的translationX的位置,然後ofFloat()方法的第二個參數傳入”translationX”,緊接着後面三個參數用于告訴系統TextView應該怎麼移動,現在運作一下代碼,效果如下圖所示:
然後我們還可以TextView進行縮放操作,比如說将TextView在垂直方向上放大3倍再還原,就可以這樣寫:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "scaleY", f, f, f);
animator.setDuration();
animator.start();
這裡将ofFloat()方法的第二個參數改成了”scaleY”,表示在垂直方向上進行縮放,現在重新運作一下程式,效果如下圖所示:
ofFloat()方法的第二個參數到底可以傳哪些值呢?目前我們使用過了alpha、rotation、translationX和scaleY這幾個值,分别可以完成淡入淡出、旋轉、水準移動、垂直縮放這幾種動畫。而實際上,這個參數可以傳入任意的值,因為ObjectAnimator再設計的時候就沒有單單針對View來設計,而是針對于任意對象的,它所負責的工作就是不斷地向某個對象中的某個屬性進行指派,然後對象根據屬性值的改變來決定如何展現出來。
比如如說我們調用下面這段代碼:
ObjectAnimator.ofFloat(textview, "alpha", , );
這段代碼的意思是ObjectAnimator會不斷地改變textView對象中alpha屬性的值,從1f變化到0f,然後textview對象需要根據alpha屬性值的改變來不斷重新整理界面的顯示,進而讓使用者可以看出淡入淡出的動畫效果。那麼textview對象中是不是有alpha屬性這個值呢?沒有,不僅textview沒有這個屬性,連它所有的父類也是沒有這個屬性的!這就奇怪了,textview當中并沒有alpha這個屬性,ObjectAnimator是如何進行操作的呢?其實 ObjectAnimator内部的工作機制并不是直接對我們傳入的屬性名進行操作的,而是會去尋找這個屬性名對應的get和set方法, 是以alpha屬性所對應的get和set方法應該就是:
public void setAlpha(float value);
public float getAlpha();
那麼textview對象中是否有這兩個方法呢?确實有,并且這兩個方法是由View對象提供的,也就是說不僅TextView可以使用這個屬性來進行淡入淡出動畫操作,任何繼承自View的對象都可以的。
既然alpha是這個樣子,相信大家一定已經明白了,前面我們所用的所有屬性都是這個工作原理,那麼View當中一定也存在着setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()這些方法,不信的話你可以到View當中去找一下。
使用組合動畫
如果想多個動畫一起使用,也非常簡單,android提供了豐富的API供我們調用。
實作組合動畫功能主要需要借助AnimatorSet這個類,(注意和 AnimationSet 差別) 這個類提供了一個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", -f, f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", f, f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", f, f, f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration();
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) {
// 在動畫被取消的時候調用
}
});
但是也許很多時候我們并不想要監聽那麼多個事件,可能我隻想要監聽動畫結束這一個事件,那麼每次都要将四個接口全部實作一遍就顯得非常繁瑣。沒關系,為此Android提供了一個擴充卡類,叫作AnimatorListenerAdapter,使用這個類就可以解決掉實作接口繁瑣的問題了,如下所示:
anim.addListener(new AnimatorListenerAdapter() {
});
這裡我們向addListener()方法中傳入這個擴充卡對象,由于AnimatorListenerAdapter中已經将每個接口都實作好了,是以這裡不用實作任何一個方法也不會報錯。那麼如果我想監聽動畫結束這個事件,就隻需要單獨重寫這一個方法就可以了,如下所示:
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
我們可以使用代碼來編寫所有的動畫功能,這也是最常用的一種做法。不過,過去的補間動畫除了使用代碼編寫之外也是可以使用XML編寫的,是以屬性動畫也提供了這一功能,即通過XML來完成和代碼一樣的屬性動畫功能。
通過XML來編寫動畫可能會比通過代碼來編寫動畫要慢一些,但是在重用方面将會變得非常輕松,比如某個将通用的動畫編寫到XML裡面,我們就可以在各個界面當中輕松去重用它。
如果想要使用XML來編寫動畫,首先要在res目錄下面建立一個animator檔案夾,所有屬性動畫的XML檔案都應該存放在這個檔案夾當中。然後在XML檔案中我們一共可以使用如下三種标簽:
-
對應代碼中的ValueAnimator<animator>
-
對應代碼中的ObjectAnimator<objectAnimator>
-
對應代碼中的AnimatorSet<set>
那麼比如說我們想要實作一個從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()方法啟動動畫就可以了。