1. 程式人生 > >Android屬性動畫深入分析:讓你成為動畫牛人

Android屬性動畫深入分析:讓你成為動畫牛人

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

前言

感謝你閱讀本文,我堅信讀完本文肯定不會讓你失望的。想要做動畫牛人?想要精通動畫?那麼本文所講的內容都是你必須要掌握的。關於動畫,我已經寫了兩篇博文,但是還是沒有將動畫描述全面,於是我寫了本文,另外,我後面還會再寫一篇屬性動畫的原始碼分析,通過這四篇博文,你將真正地成為動畫牛人。

Android動畫系列:

Android原始碼分析—屬性動畫的工作原理

我為什麼要寫這篇博文?

是分享精神,我對動畫從瞭解到熟悉是經歷了一個過程,而這一個過程是要花費時間的,也許是幾天,也許是幾個小時,總之沒有至少若干小時的時間投入,你是無法熟悉動畫的全部的。我花了大量時間來弄懂動畫的整個邏輯,深知其中的辛苦,所以,我不想大家再像我這樣,我想大家能夠更快地熟悉並精通動畫。通過本文,你將會深入瞭解Android動畫並且從此沒有動畫再能難得了你。確切來說本文是深入分析屬性動畫,因為View動畫和幀動畫的功能有限也比較簡單,沒有太多值得分析的東西。

開篇

像設計模式一樣,我們也提出一個問題來引出我們的內容。

問題:

給Button加一個動畫,讓這個Button的寬度從當前寬度增加到500px。

也許你會說,這很簡單,用漸變動畫就可以搞定,我們可以來試試,你能寫出來嗎?很快你就會恍然大悟,原來漸變動畫根本不支援對寬度進行動畫啊,沒錯,漸變動畫只支援四種類型:平移(Translate)、旋轉(Rotate)、縮放(Scale)、不透明度(Alpha)。當然你用x方向縮放(scaleX)可以讓Button在x方向放大,看起來好像是寬度增加了,實際上不是,只是Button被放大了而已,而且由於只在x方向被放大,這個時候Button的背景以及上面的文字都被拉伸了,甚至有可能Button會超出螢幕。下面是效果圖

上述效果顯然是很差的,而且也不是真正地對寬度做動畫,不過,所幸我們還有屬性動畫,我們用屬性動畫試試

看demo

  1. privatevoid performAnimate() {  
  2.     ObjectAnimator.ofInt(mButton, "width"500).setDuration(5000).start();  
  3. }  
  4. @Override
  5. publicvoid onClick(View v) {  
  6.     if (v == mButton) {  
  7.         performAnimate();  
  8.     }  
  9. }  

上述程式碼執行一下發現沒效果,其實沒效果是對的,如果你隨便傳遞一個屬性過去,輕則沒動畫效果,重則程式直接Crash。

下面分析下屬性動畫的原理:

屬性動畫要求動畫作用的物件提供該屬性的get和set方法,屬性動畫根據你傳遞的該熟悉的初始值和最終值,以動畫的效果多次去呼叫set方法,每次傳遞給set方法的值都不一樣,確切來說是隨著時間的推移,所傳遞的值越來越接近最終值。總結一下,你對object的屬性xxx做動畫,如果想讓動畫生效,要同時滿足兩個條件:

1. object必須要提供setXxx方法,如果動畫的時候沒有傳遞初始值,那麼還要提供getXxx方法,因為系統要去拿xxx屬性的初始值(如果這條不滿足,程式直接Crash)

2. object的setXxx對屬性xxx所做的改變必須能夠通過某種方法反映出來,比如會帶來ui的改變啥的(如果這條不滿足,動畫無效果但不會Crash)

以上條件缺一不可

那麼為什麼我們對Button的width屬性做動畫沒有效果?這是因為Button內部雖然提供了getWidth和setWidth方法,但是這個setWidth方法並不是改變檢視的大小,它是TextView新新增的方法,View是沒有這個setWidth方法的,由於Button繼承了TextView,所以Button也就有了setWidth方法。下面看一下這個getWidth和setWidth方法的原始碼:

  1. /** 
  2.  * Makes the TextView exactly this many pixels wide. 
  3.  * You could do the same thing by specifying this number in the 
  4.  * LayoutParams. 
  5.  * 
  6.  * @see #setMaxWidth(int) 
  7.  * @see #setMinWidth(int) 
  8.  * @see #getMinWidth() 
  9.  * @see #getMaxWidth() 
  10.  * 
  11.  * @attr ref android.R.styleable#TextView_width 
  12.  */
  13. @android.view.RemotableViewMethod  
  14. publicvoid setWidth(int pixels) {  
  15.     mMaxWidth = mMinWidth = pixels;  
  16.     mMaxWidthMode = mMinWidthMode = PIXELS;  
  17.     requestLayout();  
  18.     invalidate();  
  19. }  
  20. /** 
  21.  * Return the width of the your view. 
  22.  * 
  23.  * @return The width of your view, in pixels. 
  24.  */
  25. @ViewDebug.ExportedProperty(category = "layout")  
  26. publicfinalint getWidth() {  
  27.     return mRight - mLeft;  
  28. }  

從原始碼可以看出,getWidth的確是獲取View的寬度的,而setWidth是TextView和其子類的專屬方法,它的作用不是設定View的寬度,而是設定TextView的最大寬度和最小寬度的,這個和TextView的寬度不是一個東西,具體來說,TextView的寬度對應Xml中的android:layout_width屬性,而TextView還有一個屬性android:width,這個android:width屬性就對應了TextView的setWidth方法。好吧,我承認我的這段描述有點混亂,但事情的確是這個樣子的,而且我目前還沒發現這個android:width屬性有啥重要的用途,感覺好像沒用似的,這裡就不深究了,不然就偏離主題了。總之,TextView和Button的setWidth和getWidth乾的不是同一件事情,通過setWidth無法改變控制元件的寬度,所以對width做屬性動畫沒有效果,對應於屬性動畫的兩個條件來說,本例中動畫不生效的原因是隻滿足了條件1未滿足條件2。

針對上述問題,Google告訴我們有3中解決方法:

1. 給你的物件加上get和set方法,如果你有許可權的話

2. 用一個類來包裝原始物件,間接為其提供get和set方法

3. 採用ValueAnimator,監聽動畫過程,自己實現屬性的改變

看起來有點抽象,不過不用擔心,下面我會一一介紹。

對任何屬性做動畫

針對上面提出的三種解決方法,這裡會給出具體的介紹:

給你的物件加上get和set方法,如果你有許可權的話

這個的意思很好理解,如果你有許可權的話,加上get和set就搞定了,但是很多時候我們沒許可權去這麼做,比如本文開頭所提到的問題,你無法給Button加上一個合乎要求的setWidth方法,因為這是Android SDK內部實現的。這個方法最簡單,但是往往是不可行的,這裡就不對其進行更多分析了。

用一個類來包裝原始物件,間接為其提供get和set方法

這是一個很有用的解決方法,是我最喜歡用的,因為用起來很方便,也很好理解,下面將通過一個具體的例子來介紹它

  1. privatevoid performAnimate() {  
  2.     ViewWrapper wrapper = new ViewWrapper(mButton);  
  3.     ObjectAnimator.ofInt(wrapper, "width"500).setDuration(5000).start();  
  4. }  
  5. @Override
  6. publicvoid onClick(View v) {  
  7.     if (v == mButton) {  
  8.         performAnimate();  
  9.     }  
  10. }  
  11. privatestaticclass ViewWrapper {  
  12.     private View mTarget;  
  13.     public ViewWrapper(View target) {  
  14.         mTarget = target;  
  15.     }  
  16.     publicint getWidth() {  
  17.         return mTarget.getLayoutParams().width;  
  18.     }  
  19.     publicvoid setWidth(int width) {  
  20.         mTarget.getLayoutParams().width = width;  
  21.         mTarget.requestLayout();  
  22.     }  
  23. }  

上述程式碼5s內讓Button的寬度增加到500px,為了達到這個效果,我們提供了ViewWrapper類專門用於包裝View,具體到本例是包裝Button,然後我們對ViewWrapper的width熟悉做動畫,並且在setWidth方法中修改其內部的target的寬度,而target實際上就是我們包裝的Button,這樣一個間接屬性動畫就搞定了。上述程式碼同樣適用於一個物件的其他屬性。下面看效果


 ok,效果達到了,真正實現了對寬度做動畫。

採用ValueAnimator,監聽動畫過程,自己實現屬性的改變

首先說說啥是ValueAnimator,ValueAnimator本身不作用於任何物件,也就是說直接使用它沒有任何動畫效果。它可以對一個值做動畫,然後我們可以監聽其動畫過程,在動畫過程中修改我們的物件的屬性值,這樣也就相當於我們的物件做了動畫。還是不太明白?沒關係,下面用例子說明

  1. privatevoid performAnimate(final View target, finalint start, finalint end) {  
  2.     ValueAnimator valueAnimator = ValueAnimator.ofInt(1100);  
  3.     valueAnimator.addUpdateListener(new AnimatorUpdateListener() {  
  4.         //持有一個IntEvaluator物件,方便下面估值的時候使用
  5.         private IntEvaluator mEvaluator = new IntEvaluator();  
  6.         @Override
  7.         publicvoid onAnimationUpdate(ValueAnimator animator) {  
  8.             //獲得當前動畫的進度值,整型,1-100之間
  9.             int currentValue = (Integer)animator.getAnimatedValue();  
  10.             Log.d(TAG, "current value: " + currentValue);  
  11.             //計算當前進度佔整個動畫過程的比例,浮點型,0-1之間
  12.             float fraction = currentValue / 100f;  
  13.             //這裡我偷懶了,不過有現成的幹嗎不用呢
  14.             //直接呼叫整型估值器通過比例計算出寬度,然後再設給Button
  15.             target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);  
  16.             target.requestLayout();  
  17.         }  
  18.     });  
  19.     valueAnimator.setDuration(5000).start();  
  20. }  
  21. @Override
  22. publicvoid onClick(View v) {  
  23.     if (v == mButton) {  
  24.         performAnimate(mButton, mButton.getWidth(), 500);  
  25.     }  
  26. }  

上述程式碼的動畫效果圖和採用ViewWrapper是一樣的,請參看上圖。關於這個ValueAnimator我要再說一下,拿上例來說,它會在5000ms內將一個數從1變到100,然後動畫的每一幀會回撥onAnimationUpdate方法,在這個方法裡,我們可以獲取當前的值(1-100),根據當前值所佔的比例(當前值/100),我們可以計算出Button現在的寬度應該是多少,比如時間過了一半,當前值是50,比例為0.5,假設Button的起始寬度是100px,最終寬度是500px,那麼Button增加的寬度也應該佔總增加寬度的一半,總增加寬度是500-100=400,所以這個時候Button應該增加寬度400*0.5=200,那麼當前Button的寬度應該為初始寬度+ 增加寬度(100+200=300)。上述計算過程很簡單,其實它就是整型估值器IntEvaluator的內部實現,所有我們不用自己寫了,直接用吧。

寫在後面的話

到此為止,本文的分析基本完成,有幾點是我想再說一下的。

1.View動畫(漸變動畫)的功能是有限的,大家可以嘗試使用屬性動畫

2.為了在各種安卓版本上使用屬性動畫,你需要採用nineoldandroids,它是GitHub開源專案,jar包和原始碼都可以在網上下到,如果下不到jar包,我可以發給大家

3.再複雜的動畫都是簡單動畫的合理組合,再加上本文介紹的方法,可以對任何屬性作用動畫效果,也就是說你幾乎可以做出任何動畫

4.屬性動畫中的插值器(Interpolator)和估值器(TypeEvaluator)很重要,它是實現非勻速動畫的重要手段,你應該試著搞懂它,最好你還能夠自定義它們

5.如果你能把我這個動畫系列博文都看一遍並且理解它,我認為你對動畫絕對算得上精通,而且我不認為有面試官能夠在動畫上問倒你