Android 屬性動畫Interpolator和ViewPropertyAnimator的用法

轉載地址:http://blog.csdn.net/guolin_blog/article/details/44171115

Interpolator的用法

Interpolator這個東西很難進行翻譯,直譯過來的話是補間器的意思,它的主要作用是可以控制動畫的變化速率,比如去實現一種非線性運動的動畫效果。那麼什麼叫做非線性運動的動畫效果呢?就是說動畫改變的速率不是一成不變的,像加速運動以及減速運動都屬於非線性運動。

不過Interpolator並不是屬性動畫中新增的技術,實際上從Android 1.0版本開始就一直存在Interpolator接口了,而之前的補間動畫當然也是支持這個功能的。只不過在屬性動畫中新增了一個TimeInterpolator接口,這個接口是用於兼容之前的Interpolator的,這使得所有過去的Interpolator實現類都可以直接拿過來放到屬性動畫當中使用,那麼我們來看一下現在TimeInterpolator接口的所有實現類,如下圖所示:


可以看到,TimeInterpolator接口已經有非常多的實現類了,這些都是Android系統內置好的並且我們可以直接使用的Interpolator。每個Interpolator都有它各自的實現效果,比如說AccelerateInterpolator就是一個加速運動的Interpolator,而DecelerateInterpolator就是一個減速運動的Interpolator。

我覺得細心的朋友應該早已經發現了,在前面兩篇文章當中我們所學到的所有屬性動畫,其實都不是在進行一種線程運動。比如說在“上”篇文章中使用ValueAnimator所打印的值如下所示:


可以看到,一開始的值變化速度明顯比較慢,僅0.0開頭的就打印了4次,之後開始加速,最後階段又開始減速,因此我們可以很明顯地看出這一個先加速後減速的Interpolator。

那麼再來看一下在“中”篇文章中完成的小球移動加變色的功能,如下圖所示:


從上圖中我們明顯可以看出,小球一開始運動速度比較慢,然後逐漸加速,中間的部分運動速度就比較快,接下來開始減速,最後緩緩停住。另外顏色變化也是這種規律,一開始顏色變化的比較慢,中間顏色變化的很快,最後階段顏色變化的又比較慢。

從以上幾點我們就可以總結出一個結論了,使用屬性動畫時,系統默認的Interpolator其實就是一個先加速後減速的Interpolator,對應的實現類就是AccelerateDecelerateInterpolator。

當然,我們也可以很輕鬆地修改這一默認屬性,將它替換成任意一個系統內置好的Interpolator。就拿“中”篇文章中的代碼來舉例吧,MyAnimView中的startAnimation()方法是開啓動畫效果的入口,這裏我們對Point對象的座標稍做一下修改,讓它變成一種垂直掉落的效果,代碼如下所示:

[java] view plaincopy
  1. private void startAnimation() {  
  2.     Point startPoint = new Point(getWidth() / 2, RADIUS);  
  3.     Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);  
  4.     ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
  5.     anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
  6.         @Override  
  7.         public void onAnimationUpdate(ValueAnimator animation) {  
  8.             currentPoint = (Point) animation.getAnimatedValue();  
  9.             invalidate();  
  10.         }  
  11.     });  
  12.     anim.setDuration(2000);  
  13.     anim.start();  
  14. }  
這裏主要是對Point構造函數中的座標值進行了一下改動,那麼現在小球運動的動畫效果應該是從屏幕正中央的頂部掉落到底部。但是現在默認情況下小球的下降速度肯定是先加速後減速的,這不符合物理的常識規律,如果把小球視爲一個自由落體的話,那麼下降的速度應該是越來越快的。我們怎樣才能改變這一默認行爲呢?其實很簡單,調用Animator的setInterpolator()方法就可以了,這個方法要求傳入一個實現TimeInterpolator接口的實例,那麼比如說我們想要實現小球下降越來越快的效果,就可以使用AccelerateInterpolator,代碼如下所示:
[java] view plaincopy
  1. private void startAnimation() {  
  2.     Point startPoint = new Point(getWidth() / 2, RADIUS);  
  3.     Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);  
  4.     ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
  5.     anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
  6.         @Override  
  7.         public void onAnimationUpdate(ValueAnimator animation) {  
  8.             currentPoint = (Point) animation.getAnimatedValue();  
  9.             invalidate();  
  10.         }  
  11.     });  
  12.     anim.setInterpolator(new AccelerateInterpolator(2f));  
  13.     anim.setDuration(2500);  
  14.     anim.start();  
  15. }  

代碼很簡單,這裏調用了setInterpolator()方法,然後傳入了一個AccelerateInterpolator的實例,注意AccelerateInterpolator的構建函數可以接收一個float類型的參數,這個參數是用於控制加速度的。現在運行一下代碼,效果如下圖所示:


OK,效果非常明顯,說明我們已經成功替換掉了默認的Interpolator,AccelerateInterpolator確實是生效了。但是現在的動畫效果看上去仍然是怪怪的,因爲一個小球從很高的地方掉落到地面上直接就靜止了,這也是不符合物理規律的,小球撞擊到地面之後應該要反彈起來,然後再次落下,接着再反彈起來,又再次落下,以此反覆,最後靜止。這個功能我們當然可以自己去寫,只不過比較複雜,所幸的是,Android系統中已經提供好了這樣一種Interpolator,我們只需要簡單地替換一下就可以完成上面的描述的效果,代碼如下所示:

[java] view plaincopy
  1. private void startAnimation() {  
  2.     Point startPoint = new Point(getWidth() / 2, RADIUS);  
  3.     Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);  
  4.     ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
  5.     anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
  6.         @Override  
  7.         public void onAnimationUpdate(ValueAnimator animation) {  
  8.             currentPoint = (Point) animation.getAnimatedValue();  
  9.             invalidate();  
  10.         }  
  11.     });  
  12.     anim.setInterpolator(new BounceInterpolator());  
  13.     anim.setDuration(3000);  
  14.     anim.start();  
  15. }  
可以看到,我們只是將設置的Interpolator換成了BounceInterpolator的實例,而BounceInterpolator就是一種可以模擬物理規律,實現反覆彈起效果的Interpolator。另外還將整體的動畫時間稍微延長了一點,因爲小球反覆彈起需要比之前更長的時間。現在重新運行一下代碼,效果如下圖所示:


OK!效果還是非常不錯的。那麼這裏我們只是選了幾個系統實現好的Interpolator,由於內置Interpolator非常多,就不一一進行講解了,大家可以自己去使用一下其它的幾種Interpolator來看一看效果。

但是,只會用一下系統提供好的Interpolator,我們顯然對自己的要求就太低了,既然是學習屬性動畫的高級用法,那麼自然要將它研究透了。下面我們就來看一下Interpolator的內部實現機制是什麼樣的,並且來嘗試寫一個自定義的Interpolator。

首先看一下TimeInterpolator的接口定義,代碼如下所示:

[java] view plaincopy
  1. /** 
  2.  * A time interpolator defines the rate of change of an animation. This allows animations 
  3.  * to have non-linear motion, such as acceleration and deceleration. 
  4.  */  
  5. public interface TimeInterpolator {  
  6.   
  7.     /** 
  8.      * Maps a value representing the elapsed fraction of an animation to a value that represents 
  9.      * the interpolated fraction. This interpolated value is then multiplied by the change in 
  10.      * value of an animation to derive the animated value at the current elapsed animation time. 
  11.      * 
  12.      * @param input A value between 0 and 1.0 indicating our current point 
  13.      *        in the animation where 0 represents the start and 1.0 represents 
  14.      *        the end 
  15.      * @return The interpolation value. This value can be more than 1.0 for 
  16.      *         interpolators which overshoot their targets, or less than 0 for 
  17.      *         interpolators that undershoot their targets. 
  18.      */  
  19.     float getInterpolation(float input);  
  20. }  

OK,接口還是非常簡單的,只有一個getInterpolation()方法。大家有興趣可以通過註釋來對這個接口進行詳解的瞭解,這裏我就簡單解釋一下,getInterpolation()方法中接收一個input參數,這個參數的值會隨着動畫的運行而不斷變化,不過它的變化是非常有規律的,就是根據設定的動畫時長勻速增加,變化範圍是0到1。也就是說當動畫一開始的時候input的值是0,到動畫結束的時候input的值是1,而中間的值則是隨着動畫運行的時長在0到1之間變化的。

說到這個input的值,我覺得有不少朋友可能會聯想到我們在“中”篇文章中使用過的fraction值。那麼這裏的input和fraction有什麼關係或者區別呢?答案很簡單,input的值決定了fraction的值。input的值是由系統經過計算後傳入到getInterpolation()方法中的,然後我們可以自己實現getInterpolation()方法中的算法,根據input的值來計算出一個返回值,而這個返回值就是fraction了。

因此,最簡單的情況就是input值和fraction值是相同的,這種情況由於input值是勻速增加的,因而fraction的值也是勻速增加的,所以動畫的運動情況也是勻速的。系統中內置的LinearInterpolator就是一種勻速運動的Interpolator,那麼我們來看一下它的源碼是怎麼實現的:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * An interpolator where the rate of change is constant 
  3.  */  
  4. @HasNativeInterpolator  
  5. public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {  
  6.   
  7.     public LinearInterpolator() {  
  8.     }  
  9.   
  10.     public LinearInterpolator(Context context, AttributeSet attrs) {  
  11.     }  
  12.   
  13.     public float getInterpolation(float input) {  
  14.         return input;  
  15.     }  
  16.   
  17.     /** @hide */  
  18.     @Override  
  19.     public long createNativeInterpolator() {  
  20.         return NativeInterpolatorFactoryHelper.createLinearInterpolator();  
  21.     }  
  22. }  

這裏我們只看getInterpolation()方法,這個方法沒有任何邏輯,就是把參數中傳遞的input值直接返回了,因此fraction的值就是等於input的值的,這就是勻速運動的Interpolator的實現方式。

當然這是最簡單的一種Interpolator的實現了,我們再來看一個稍微複雜一點的。既然現在大家都知道了系統在默認情況下使用的是AccelerateDecelerateInterpolator,那我們就來看一下它的源碼吧,如下所示:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * An interpolator where the rate of change starts and ends slowly but 
  3.  * accelerates through the middle. 
  4.  *  
  5.  */  
  6. @HasNativeInterpolator  
  7. public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {  
  8.     public AccelerateDecelerateInterpolator() {  
  9.     }  
  10.       
  11.     @SuppressWarnings({"UnusedDeclaration"})  
  12.     public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {  
  13.     }  
  14.       
  15.     public float getInterpolation(float input) {  
  16.         return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;  
  17.     }  
  18.   
  19.     /** @hide */  
  20.     @Override  
  21.     public long createNativeInterpolator() {  
  22.         return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();  
  23.     }  
  24. }  
代碼雖然沒有變長很多,但是getInterpolation()方法中的邏輯已經明顯變複雜了,不再是簡單地將參數中的input進行返回,而是進行了一個較爲複雜的數學運算。那這裏我們來分析一下它的算法實現,可以看到,算法中主要使用了餘弦函數,由於input的取值範圍是0到1,那麼cos函數中的取值範圍就是π到2π。而cos(π)的結果是-1,cos(2π)的結果是1,那麼這個值再除以2加上0.5之後,getInterpolation()方法最終返回的結果值還是在0到1之間。只不過經過了餘弦運算之後,最終的結果不再是勻速增加的了,而是經歷了一個先加速後減速的過程。我們可以將這個算法的執行情況通過曲線圖的方式繪製出來,結果如下圖所示:


可以看到,這是一個S型的曲線圖,當橫座標從0變化到0.2的時候,縱座標的變化幅度很小,但是之後就開始明顯加速,最後橫座標從0.8變化到1的時候,縱座標的變化幅度又變得很小。

OK,通過分析LinearInterpolator和AccelerateDecelerateInterpolator的源碼,我們已經對Interpolator的內部實現機制有了比較清楚的認識了,那麼接下來我們就開始嘗試編寫一個自定義的Interpolator。

編寫自定義Interpolator最主要的難度都是在於數學計算方面的,由於我數學並不是很好,因此這裏也就寫一個簡單點的Interpolator來給大家演示一下。既然屬性動畫默認的Interpolator是先加速後減速的一種方式,這裏我們就對它進行一個簡單的修改,讓它變成先減速後加速的方式。新建DecelerateAccelerateInterpolator類,讓它實現TimeInterpolator接口,代碼如下所示:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class DecelerateAccelerateInterpolator implements TimeInterpolator{  
  2.   
  3.     @Override  
  4.     public float getInterpolation(float input) {  
  5.         float result;  
  6.         if (input <= 0.5) {  
  7.             result = (float) (Math.sin(Math.PI * input)) / 2;  
  8.         } else {  
  9.             result = (float) (2 - Math.sin(Math.PI * input)) / 2;  
  10.         }  
  11.         return result;  
  12.     }  
  13.   
  14. }  
這段代碼是使用正弦函數來實現先減速後加速的功能的,因爲正弦函數初始弧度的變化值非常大,剛好和餘弦函數是相反的,而隨着弧度的增加,正弦函數的變化值也會逐漸變小,這樣也就實現了減速的效果。當弧度大於π/2之後,整個過程相反了過來,現在正弦函數的弧度變化值非常小,漸漸隨着弧度繼續增加,變化值越來越大,弧度到π時結束,這樣從0過度到π,也就實現了先減速後加速的效果。

同樣我們可以將這個算法的執行情況通過曲線圖的方式繪製出來,結果如下圖所示:


可以看到,這也是一個S型的曲線圖,只不過曲線的方向和剛纔是相反的。從上圖中我們可以很清楚地看出來,一開始縱座標的變化幅度很大,然後逐漸變小,橫座標到0.5的時候縱座標變化幅度趨近於零,之後隨着橫座標繼續增加縱座標的變化幅度又開始變大,的確是先減速後加速的效果。

那麼現在我們將DecelerateAccelerateInterpolator在代碼中進行替換,如下所示:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void startAnimation() {  
  2.     Point startPoint = new Point(getWidth() / 2, RADIUS);  
  3.     Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);  
  4.     ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
  5.     anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
  6.         @Override  
  7.         public void onAnimationUpdate(ValueAnimator animation) {  
  8.             currentPoint = (Point) animation.getAnimatedValue();  
  9.             invalidate();  
  10.         }  
  11.     });  
  12.     anim.setInterpolator(new DecelerateAccelerateInterpolator());  
  13.     anim.setDuration(3000);  
  14.     anim.start();  
  15. }  

非常簡單,就是將DecelerateAccelerateInterpolator的實例傳入到setInterpolator()方法當中。重新運行一下代碼,效果如下圖所示:


OK!小球的運動確實是先減速後加速的效果,說明我們自定義的Interpolator已經可以正常工作了。通過這樣一個程度的學習,相信大家對屬性動畫Interpolator的理解和使用都達到了一個比較深刻的層次了。

ViewPropertyAnimator的用法

ViewPropertyAnimator其實算不上什麼高級技巧,它的用法格外的簡單,只不過和前面所學的所有屬性動畫的知識不同,它並不是在3.0系統當中引入的,而是在3.1系統當中附增的一個新的功能,因此這裏我們把它作爲整個屬性動畫系列的收尾部分。

我們都知道,屬性動畫的機制已經不是再針對於View而進行設計的了,而是一種不斷地對值進行操作的機制,它可以將值賦值到指定對象的指定屬性上。但是,在絕大多數情況下,我相信大家主要都還是對View進行動畫操作的。Android開發團隊也是意識到了這一點,沒有爲View的動畫操作提供一種更加便捷的用法確實是有點太不人性化了,於是在Android 3.1系統當中補充了ViewPropertyAnimator這個機制。

那我們先來回顧一下之前的用法吧,比如我們想要讓一個TextView從常規狀態變成透明狀態,就可以這樣寫:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f);  
  2. animator.start();  
看上去複雜嗎?好像也不怎麼複雜,但確實也不怎麼容易理解。我們要將操作的view、屬性、變化的值都一起傳入到ObjectAnimator.ofFloat()方法當中,雖然看上去也沒寫幾行代碼,但這不太像是我們平時使用的面向對象的思維。

那麼下面我們就來看一下如何使用ViewPropertyAnimator來實現同樣的效果,ViewPropertyAnimator提供了更加易懂、更加面向對象的API,如下所示:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. textview.animate().alpha(0f);  

果然非常簡單!不過textview.animate()這個方法是怎麼回事呢?animate()方法就是在Android 3.1系統上新增的一個方法,這個方法的返回值是一個ViewPropertyAnimator對象,也就是說拿到這個對象之後我們就可以調用它的各種方法來實現動畫效果了,這裏我們調用了alpha()方法並轉入0,表示將當前的textview變成透明狀態。

怎麼樣?比起使用ObjectAnimator,ViewPropertyAnimator的用法明顯更加簡單易懂吧。除此之外,ViewPropertyAnimator還可以很輕鬆地將多個動畫組合到一起,比如我們想要讓textview運動到500,500這個座標點上,就可以這樣寫:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. textview.animate().x(500).y(500);  
可以看出,ViewPropertyAnimator是支持連綴用法的,我們想讓textview移動到橫座標500這個位置上時調用了x(500)這個方法,然後讓textview移動到縱座標500這個位置上時調用了y(500)這個方法,將所有想要組合的動畫通過這種連綴的方式拼接起來,這樣全部動畫就都會一起被執行。

那麼怎樣去設定動畫的運行時長呢?很簡單,也是通過連綴的方式設定即可,比如我們想要讓動畫運行5秒鐘,就可以這樣寫:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. textview.animate().x(500).y(500).setDuration(5000);  
除此之外,本篇文章第一部分所學的Interpolator技術我們也可以應用在ViewPropertyAnimator上面,如下所示:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. textview.animate().x(500).y(500).setDuration(5000)  
  2.         .setInterpolator(new BounceInterpolator());  
用法很簡單,同樣也是使用連綴的方式。相信大家現在都已經體驗出來了,ViewPropertyAnimator其實並沒有什麼太多的技巧可言,用法基本都是大同小異的,需要用到什麼功能就連綴一下,因此更多的用法大家只需要去查閱一下文檔,看看還支持哪些功能,有哪些接口可以調用就可以了。

那麼除了用法之外,關於ViewPropertyAnimator有幾個細節還是值得大家注意一下的:

  • 整個ViewPropertyAnimator的功能都是建立在View類新增的animate()方法之上的,這個方法會創建並返回一個ViewPropertyAnimator的實例,之後的調用的所有方法,設置的所有屬性都是通過這個實例完成的。
  • 大家注意到,在使用ViewPropertyAnimator時,我們自始至終沒有調用過start()方法,這是因爲新的接口中使用了隱式啓動動畫的功能,只要我們將動畫定義完成之後,動畫就會自動啓動。並且這個機制對於組合動畫也同樣有效,只要我們不斷地連綴新的方法,那麼動畫就不會立刻執行,等到所有在ViewPropertyAnimator上設置的方法都執行完畢後,動畫就會自動啓動。當然如果不想使用這一默認機制的話,我們也可以顯式地調用start()方法來啓動動畫。
  • ViewPropertyAnimator的所有接口都是使用連綴的語法來設計的,每個方法的返回值都是它自身的實例,因此調用完一個方法之後可以直接連綴調用它的另一個方法,這樣把所有的功能都串接起來,我們甚至可以僅通過一行代碼就完成任意複雜度的動畫功能。

發佈了61 篇原創文章 · 獲贊 7 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章