1. 程式人生 > >Android屬性動畫完全解析(上),初識屬性動畫的基本用法

Android屬性動畫完全解析(上),初識屬性動畫的基本用法

fcm 操作 fad 擴展性 改變 內部使用 如果 轉載 @override

轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/43536355

在手機上去實現一些動畫效果算是件比較炫酷的事情,因此Android系統在一開始的時候就給我們提供了兩種實現動畫效果的方式,逐幀動畫(frame-by-frame animation)和補間動畫(tweened animation)。逐幀動畫的工作原理很簡單,其實就是將一個完整的動畫拆分成一張張單獨的圖片,然後再將它們連貫起來進行播放,類似於動畫片的工作原理。補間動畫則是可以對View進行一系列的動畫操作,包括淡入淡出、縮放、平移、旋轉四種。

然而自Android 3.0版本開始,系統給我們提供了一種全新的動畫模式,屬性動畫(property animation),它的功能非常強大,彌補了之前補間動畫的一些缺陷,幾乎是可以完全替代掉補間動畫了。對於逐幀動畫和補間動畫的用法,我不想再多講,它們的技術已經比較老了,而且網上資料也非常多,那麽今天我們這篇文章的主題就是對Android屬性動畫進行一次完全解析。

為什麽要引入屬性動畫?

Android之前的補間動畫機制其實還算是比較健全的,在android.view.animation包下面有好多的類可以供我們操作,來完成一系列的動畫效果,比如說對View進行移動、縮放、旋轉和淡入淡出,並且我們還可以借助AnimationSet來將這些動畫效果組合起來使用,除此之外還可以通過配置Interpolator來控制動畫的播放速度等等等等。那麽這裏大家可能要產生疑問了,既然之前的動畫機制已經這麽健全了,為什麽還要引入屬性動畫呢?

其實上面所謂的健全都是相對的,如果你的需求中只需要對View進行移動、縮放、旋轉和淡入淡出操作,那麽補間動畫確實已經足夠健全了。但是很顯然,這些功能是不足以覆蓋所有的場景的,一旦我們的需求超出了移動、縮放、旋轉和淡入淡出這四種對View的操作,那麽補間動畫就不能再幫我們忙了,也就是說它在功能和可擴展方面都有相當大的局限性,那麽下面我們就來看看補間動畫所不能勝任的場景。

註意上面我在介紹補間動畫的時候都有使用“對View進行操作”這樣的描述,沒錯,補間動畫是只能夠作用在View上的。也就是說,我們可以對一個Button、TextView、甚至是LinearLayout、或者其它任何繼承自View的組件進行動畫操作,但是如果我們想要對一個非View的對象進行動畫操作,抱歉,補間動畫就幫不上忙了。可能有的朋友會感到不能理解,我怎麽會需要對一個非View的對象進行動畫操作呢?這裏我舉一個簡單的例子,比如說我們有一個自定義的View,在這個View當中有一個Point對象用於管理坐標,然後在onDraw()方法當中就是根據這個Point對象的坐標值來進行繪制的。也就是說,如果我們可以對Point對象進行動畫操作,那麽整個自定義View的動畫效果就有了。顯然,補間動畫是不具備這個功能的,這是它的第一個缺陷。

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

最後,補間動畫還有一個致命的缺陷,就是它只是改變了View的顯示效果而已,而不會真正去改變View的屬性。什麽意思呢?比如說,現在屏幕的左上角有一個按鈕,然後我們通過補間動畫將它移動到了屏幕的右下角,現在你可以去嘗試點擊一下這個按鈕,點擊事件是絕對不會觸發的,因為實際上這個按鈕還是停留在屏幕的左上角,只不過補間動畫將這個按鈕繪制到了屏幕的右下角而已。

也正是因為這些原因,Android開發團隊決定在3.0版本當中引入屬性動畫這個功能,那麽屬性動畫是不是就把上述的問題全部解決掉了?下面我們就來一起看一看。

新引入的屬性動畫機制已經不再是針對於View來設計的了,也不限定於只能實現移動、縮放、旋轉和淡入淡出這幾種動畫操作,同時也不再只是一種視覺上的動畫效果了。它實際上是一種不斷地對值進行操作的機制,並將值賦值到指定對象的指定屬性上,可以是任意對象的任意屬性。所以我們仍然可以將一個View進行移動或者縮放,但同時也可以對自定義View中的Point對象進行動畫操作了。我們只需要告訴系統動畫的運行時長,需要執行哪種類型的動畫,以及動畫的初始值和結束值,剩下的工作就可以全部交給系統去完成了。

既然屬性動畫的實現機制是通過對目標對象進行賦值並修改其屬性來實現的,那麽之前所說的按鈕顯示的問題也就不復存在了,如果我們通過屬性動畫來移動一個按鈕,那麽這個按鈕就是真正的移動了,而不再是僅僅在另外一個位置繪制了而已。

好了,介紹了這麽多,相信大家已經對屬性動畫有了一個最基本的認識了,下面我們就來開始學習一下屬性動畫的用法。

ValueAnimator

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

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

[java] view plain copy
  1. ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
  2. anim.setDuration(300);
  3. anim.start();

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

用法就是這麽簡單,現在如果你運行一下上面的代碼,動畫就會執行了。可是這只是一個將值從0過渡到1的動畫,又看不到任何界面效果,我們怎樣才能知道這個動畫是不是已經真正運行了呢?這就需要借助監聽器來實現了,如下所示:

[java] view plain copy
  1. ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
  2. anim.setDuration(300);
  3. anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  4. @Override
  5. public void onAnimationUpdate(ValueAnimator animation) {
  6. float currentValue = (float) animation.getAnimatedValue();
  7. Log.d("TAG", "cuurent value is " + currentValue);
  8. }
  9. });
  10. anim.start();

可以看到,這裏我們通過addUpdateListener()方法來添加一個動畫的監聽器,在動畫執行的過程中會不斷地進行回調,我們只需要在回調方法當中將當前的值取出並打印出來,就可以知道動畫有沒有真正運行了。運行上述代碼,控制臺打印如下所示:

技術分享

從打印日誌的值我們就可以看出,ValueAnimator確實已經在正常工作了,值在300毫秒的時間內從0平滑過渡到了1,而這個計算工作就是由ValueAnimator幫助我們完成的。另外ofFloat()方法當中是可以傳入任意多個參數的,因此我們還可以構建出更加復雜的動畫邏輯,比如說將一個值在5秒內從0過渡到5,再過渡到3,再過渡到10,就可以這樣寫:

[java] view plain copy
  1. ValueAnimator anim = ValueAnimator.ofFloat(0f, 5f, 3f, 10f);
  2. anim.setDuration(5000);
  3. anim.start();

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

[java] view plain copy 技術分享技術分享
  1. ValueAnimator anim = ValueAnimator.ofInt(0, 100);

ValueAnimator當中最常用的應該就是ofFloat()和ofInt()這兩個方法了,另外還有一個ofObject()方法,我會在下篇文章進行講解。

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

ObjectAnimator

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

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

[java] view plain copy 技術分享技術分享
  1. ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
  2. animator.setDuration(5000);
  3. animator.start();

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

技術分享

學會了這一個用法之後,其它的用法我們就可以舉一反三了,那比如說我們想要將TextView進行一次360度的旋轉,就可以這樣寫:

[java] view plain copy 技術分享技術分享
  1. ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
  2. animator.setDuration(5000);
  3. animator.start();

可以看到,這裏我們將第二個參數改成了"rotation",然後將動畫的初始值和結束值分別設置成0和360,現在運行一下代碼,效果如下圖所示:

技術分享

那麽如果想要將TextView先向左移出屏幕,然後再移動回來,就可以這樣寫:

[java] view plain copy 技術分享技術分享
  1. float curTranslationX = textview.getTranslationX();
  2. ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "translationX", curTranslationX, -500f, curTranslationX);
  3. animator.setDuration(5000);
  4. animator.start();

這裏我們先是調用了TextView的getTranslationX()方法來獲取到當前TextView的translationX的位置,然後ofFloat()方法的第二個參數傳入"translationX",緊接著後面三個參數用於告訴系統TextView應該怎麽移動,現在運行一下代碼,效果如下圖所示:

技術分享

然後我們還可以TextView進行縮放操作,比如說將TextView在垂直方向上放大3倍再還原,就可以這樣寫:

[java] view plain copy 技術分享技術分享
  1. ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "scaleY", 1f, 3f, 1f);
  2. animator.setDuration(5000);
  3. animator.start();

這裏將ofFloat()方法的第二個參數改成了"scaleY",表示在垂直方向上進行縮放,現在重新運行一下程序,效果如下圖所示:

技術分享

到目前為止,ObjectAnimator的用法還算是相當簡單吧,但是我相信肯定會有不少朋友現在心裏都有同樣一個疑問,就是ofFloat()方法的第二個參數到底可以傳哪些值呢?目前我們使用過了alpha、rotation、translationX和scaleY這幾個值,分別可以完成淡入淡出、旋轉、水平移動、垂直縮放這幾種動畫,那麽還有哪些值是可以使用的呢?其實這個問題的答案非常玄乎,就是我們可以傳入任意的值到ofFloat()方法的第二個參數當中。任意的值?相信這很出乎大家的意料吧,但事實就是如此。因為ObjectAnimator在設計的時候就沒有針對於View來進行設計,而是針對於任意對象的,它所負責的工作就是不斷地向某個對象中的某個屬性進行賦值,然後對象根據屬性值的改變再來決定如何展現出來。

那麽比如說我們調用下面這樣一段代碼:

[java] view plain copy 技術分享技術分享
  1. ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);

其實這段代碼的意思就是ObjectAnimator會幫我們不斷地改變textview對象中alpha屬性的值,從1f變化到0f。然後textview對象需要根據alpha屬性值的改變來不斷刷新界面的顯示,從而讓用戶可以看出淡入淡出的動畫效果。

那麽textview對象中是不是有alpha屬性這個值呢?沒有,不僅textview沒有這個屬性,連它所有的父類也是沒有這個屬性的!這就奇怪了,textview當中並沒有alpha這個屬性,ObjectAnimator是如何進行操作的呢?其實ObjectAnimator內部的工作機制並不是直接對我們傳入的屬性名進行操作的,而是會去尋找這個屬性名對應的get和set方法,因此alpha屬性所對應的get和set方法應該就是:

[java] view plain copy 技術分享技術分享
  1. public void setAlpha(float value);
  2. public float getAlpha();

那麽textview對象中是否有這兩個方法呢?確實有,並且這兩個方法是由View對象提供的,也就是說不僅TextView可以使用這個屬性來進行淡入淡出動畫操作,任何繼承自View的對象都可以的。

既然alpha是這個樣子,相信大家一定已經明白了,前面我們所用的所有屬性都是這個工作原理,那麽View當中一定也存在著setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()這些方法,不信的話你可以到View當中去找一下。

組合動畫

獨立的動畫能夠實現的視覺效果畢竟是相當有限的,因此將多個動畫組合到一起播放就顯得尤為重要。幸運的是,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度,旋轉的同時進行淡入淡出操作,就可以這樣寫:

[java] view plain copy 技術分享技術分享
  1. ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
  2. ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
  3. ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
  4. AnimatorSet animSet = new AnimatorSet();
  5. animSet.play(rotate).with(fadeInOut).after(moveIn);
  6. animSet.setDuration(5000);
  7. animSet.start();

可以看到,這裏我們先是把三個動畫的對象全部創建出來,然後new出一個AnimatorSet對象之後將這三個動畫對象進行播放排序,讓旋轉和淡入淡出動畫同時進行,並把它們插入到了平移動畫的後面,最後是設置動畫時長以及啟動動畫。運行一下上述代碼,效果如下圖所示:

技術分享

Animator監聽器

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

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

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

[java] view plain copy 技術分享技術分享
  1. anim.addListener(new AnimatorListener() {
  2. @Override
  3. public void onAnimationStart(Animator animation) {
  4. }
  5. @Override
  6. public void onAnimationRepeat(Animator animation) {
  7. }
  8. @Override
  9. public void onAnimationEnd(Animator animation) {
  10. }
  11. @Override
  12. public void onAnimationCancel(Animator animation) {
  13. }
  14. });

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

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

[java] view plain copy 技術分享技術分享
  1. anim.addListener(new AnimatorListenerAdapter() {
  2. });

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

[java] view plain copy 技術分享技術分享
  1. anim.addListener(new AnimatorListenerAdapter() {
  2. @Override
  3. public void onAnimationEnd(Animator animation) {
  4. }
  5. });

使用XML編寫動畫

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

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

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

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

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

[html] view plain copy 技術分享技術分享
  1. <animator xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:valueFrom="0"
  3. android:valueTo="100"
  4. android:valueType="intType"/>

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

[html] view plain copy 技術分享技術分享
  1. <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:valueFrom="1"
  3. android:valueTo="0"
  4. android:valueType="floatType"
  5. android:propertyName="alpha"/>

其實XML編寫動畫在可讀性方面還是挺高的,上面的內容相信不用我做解釋大家也都看得懂吧。

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

[html] view plain copy 技術分享技術分享
  1. <set xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:ordering="sequentially" >
  3. <objectAnimator
  4. android:duration="2000"
  5. android:propertyName="translationX"
  6. android:valueFrom="-500"
  7. android:valueTo="0"
  8. android:valueType="floatType" >
  9. </objectAnimator>
  10. <set android:ordering="together" >
  11. <objectAnimator
  12. android:duration="3000"
  13. android:propertyName="rotation"
  14. android:valueFrom="0"
  15. android:valueTo="360"
  16. android:valueType="floatType" >
  17. </objectAnimator>
  18. <set android:ordering="sequentially" >
  19. <objectAnimator
  20. android:duration="1500"
  21. android:propertyName="alpha"
  22. android:valueFrom="1"
  23. android:valueTo="0"
  24. android:valueType="floatType" >
  25. </objectAnimator>
  26. <objectAnimator
  27. android:duration="1500"
  28. android:propertyName="alpha"
  29. android:valueFrom="0"
  30. android:valueTo="1"
  31. android:valueType="floatType" >
  32. </objectAnimator>
  33. </set>
  34. </set>
  35. </set>

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

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

[java] view plain copy 技術分享技術分享
  1. Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);
  2. animator.setTarget(view);
  3. animator.start();

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

好的,通過本篇文章的學習,我相信大家已經對屬性動畫的基本用法已經有了一個相當不錯的認識,並把最常用的一些功能都掌握好了,那麽本篇文章的內容就到這裏,下篇文章當中會繼續介紹屬性動畫,講解ValueAnimator和ObjectAnimator的高級用法,感興趣的朋友請繼續閱讀 Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的高級用法

Android屬性動畫完全解析(上),初識屬性動畫的基本用法