Android動畫深入原理分析

Android動畫深入原理分析

動畫分類

Android動畫可以分3種:View動畫,幀動畫和屬性動畫;屬性動畫爲API11的新特性,在低版本是無法直接使用屬性動畫的,但可以用nineoldAndroids來實現(但是本質還是viiew動畫)。學習本篇內容主要掌握以下知識:

1,View動畫以及自定義View動畫。
2,View動畫的一些特殊使用場景。
3,對屬性動畫做了一個全面的介紹。
4,使用動畫的一些注意事項。


view動畫

View動畫的四種變換效果對應着Animation的四個子類:TranslateAnimation(平移動畫)、ScaleAnimation(縮放動畫)、RotateAnimation(旋轉動畫)和AlphaAnimation(透明度動畫),他們即可以用代碼來動態創建也可以用XML來定義,推薦使用可讀性更好的XML來定義。 <set>標籤表示動畫集合,對應AnimationSet類,它可以包含若干個動畫,並且他的內部也可以嵌套其他動畫集合。android:interpolator 表示動畫集合所採用的插值器,插值器影響動畫速度,比如非勻速動畫就需要通過插值器來控制動畫的播放過程。
android:shareInterpolator表示集合中的動畫是否和集合共享同一個插值器,如果集合不指定插值器,那麼子動畫就需要單獨指定所需的插值器或默認值。 Animation通過setAnimationListener方法可以給View動畫添加過程監聽。 自定義View動畫只需要繼承Animation這個抽象類,並重寫initialize和applyTransformation方法,在initialize方法中做一些初始化工作,在applyTransformation中進行相應的矩形變換,很多時候需要採用Camera來簡化矩形變換過程。 幀動畫是順序播放一組預先定義好的圖片,類似電影播放;使用簡單但容易引發OOM,儘量避免使用過多尺寸較大的圖片。

view動畫應用場景

LayoutAnimation作用於ViewGroup,爲ViewGroup指定一個動畫,當他的子元素出場的時候都會具有這種動畫,ListView上用的多,LayoutAnimation也是一個View動畫。
代碼實現:

[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"  
  3.  android:animationOrder="normal"  
  4.  android:delay="0.3" android:animation="@anim/anim_item"/>  
  5.   
  6. //--- animationOrder 表示子元素的動畫的順序,有三種選項:  
  7. //normal(順序顯示)、reverse(逆序顯示)和random(隨機顯示)。  
  8.   
  9. <?xml version="1.0" encoding="utf-8"?>  
  10. <set xmlns:android="http://schemas.android.com/apk/res/android"  
  11.  android:duration="300"  
  12.  android:shareInterpolator="true">  
  13.  <alpha  
  14.      android:fromAlpha="0.0"  
  15.      android:toAlpha="1.0" />  
  16.  <translate  
  17.      android:fromXDelta="300"  
  18.      android:toXDelta="0" />  
  19. </set>  
第一種,在佈局中引用LayoutAnimation
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. <ListView  
  2.      android:id="@+id/lv"  
  3.      android:layout_width="match_parent"  
  4.      android:layout_height="0dp"  
  5.      android:layout_weight="1"  
  6.      android:layoutAnimation="@anim/anim_layout"/>  
第二種,代碼種使用
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);  
  2. LayoutAnimationController controller = new LayoutAnimationController(animation);  
  3. controller.setDelay(0.5f);  
  4. controller.setOrder(LayoutAnimationController.ORDER_NORMAL);  
  5. listview.setLayoutAnimation(controller);  

幀動畫

  逐幀動畫(Frame-by-frame Animations)從字面上理解就是一幀挨着一幀的播放圖片,類似於播放電影的效果。不同於View動畫,Android系統提供了一個類AnimationDrawable來實現幀動畫,幀動畫比較簡單,我們看一個例子就行了。
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <animation-list xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:oneshot="false">  
  4.   
  5.     <item  
  6.         android:drawable="@mipmap/lottery_1"  
  7.         android:duration="200" />  
  8.   // ...省略很多  
  9.     <item  
  10.         android:drawable="@mipmap/lottery_6"  
  11.         android:duration="200" />  
  12.   
  13. </animation-list>  

然後
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. imageView.setImageResource(R.drawable.frame_anim);  
  2. AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();  
  3. animationDrawable.start();//啓動start,關閉stop  

屬性動畫

屬性動畫是Android 3.0新加入(api 11)的功能,不同於之前的view動畫(看過的都知道,view動畫比如實現的位移其實不是真正的位置移動,只是實現了一些簡單的視覺效果)。屬性動畫對之前的動畫做了很大的拓展,毫不誇張的說,屬性動畫可以實現任何動畫效果,因爲在作用的對象是屬性(對象),屬性動畫中有幾個概念需要我們注意下,

ValueAnimator、ObjectAnimator、AnimatorSet等。

屬性動畫作用屬性

1,屬性動畫可以對任意對象的屬性進行動畫而不僅僅是View,屬性動畫默認間隔300ms,默認幀率10ms/幀。
2,看一段代碼
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. <set  
  2.   android:ordering=["together" | "sequentially"]>  
  3.   
  4.     <objectAnimator  
  5.         android:propertyName="string"  
  6.         android:duration="int"  
  7.         android:valueFrom="float | int | color"  
  8.         android:valueTo="float | int | color"  
  9.         android:startOffset="int"  
  10.         android:repeatCount="int"  
  11.         android:repeatMode=["repeat" | "reverse"]  
  12.         android:valueType=["intType" | "floatType"]/>  
  13.   
  14.     <animator  
  15.         android:duration="int"  
  16.         android:valueFrom="float | int | color"  
  17.         android:valueTo="float | int | color"  
  18.         android:startOffset="int"  
  19.         android:repeatCount="int"  
  20.         android:repeatMode=["repeat" | "reverse"]  
  21.         android:valueType=["intType" | "floatType"]/>  
  22.   
  23.     <set>  
  24.         ...  
  25.     </set>  
  26. </set>  

<set>
它代表的就是一個AnimatorSet對象。裏面有一個 ordering屬性,主要是指定動畫的播放順序。

<objectAnimator> 
它表示一個ObjectAnimator對象。它裏面有很多屬性,我們重點需要了解的也是它。
android:propertyName -------屬性名稱,例如一個view對象的”alpha”和”backgroundColor”。
android:valueFrom   --------變化開始值
android:valueTo ------------變化結束值
android:valueType -------變化值類型 ,它有兩種值:intType和floatType,默認值floatType。
android:duration ---------持續時間
android:startOffset ---------動畫開始延遲時間
android:repeatCount --------重複次數,-1表示無限重複,默認爲-1
android:repeatMode 重複模式,前提是android:repeatCount爲-1 ,它有兩種值:”reverse”和”repeat”,分別表示反向和順序方向。

<animator>
它對應的就是ValueAnimator對象。它主要有以下屬性。
android:valueFrom
android:valueTo
android:duration
android:startOffset
android:repeatCount
android:repeatMode
android:valueType

定義了一組動畫之後,我們怎麼讓它運行起來呢?
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,  
  2.     R.anim.property_animator);  
  3. set.setTarget(myObject);//myObject表示作用的對象  
  4. set.start();  

插值器和估值器

時間插值器(TimeInterpolator)的作用是根據時間流逝的百分比來計算出當前屬性值改變的百分比,系統預置的有LinearInterpolator(線性插值器:勻速動畫),AccelerateDecelerateInterpolator(加速減速插值器:動畫兩頭慢中間快),DecelerateInterpolator(減速插值器:動畫越來越慢)。

注:這裏的插值器很多,可以翻看我之前關於插值器的講解。

估值器(TypeEvaluator)的作用是根據當前屬性改變的百分比來計算改變後的屬性值。系統預置有IntEvaluator 、FloatEvaluator 、ArgbEvaluator。

舉個簡單的例子吧
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public class IntEvaluator implements TypeEvaluator<Integer> {  
  2.  public Integer evaluate(float fraction, Integer startValue, Integer endValue) {  
  3.      int startInt = startValue;  
  4.      return (int)(startInt + fraction * (endValue - startInt));  
  5.  }  
  6. }  
上述代碼就是計算當前屬性所佔總共的百分百。

插值器和估值器除了系統提供之外,我們還可以自定義實現,自定義插值器需要實現Interpolator或者TimeInterpolator;自定義估值器算法需要實現TypeEvaluator。

屬性動畫監聽器

屬性動畫監聽器用於監聽動畫的播放過程,主要有兩個接口:AnimatorUpdateListener和AnimatorListener 。
AnimatorListener 
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public static interface AnimatorListener {  
  2.     void onAnimationStart(Animator animation); //動畫開始  
  3.     void onAnimationEnd(Animator animation); //動畫結束  
  4.     void onAnimationCancel(Animator animation); //動畫取消  
  5.     void onAnimationRepeat(Animator animation); //動畫重複播放  
  6. }  
AnimatorUpdateListener
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public static interface AnimatorUpdateListener {  
  2.     void onAnimationUpdate(ValueAnimator animator);  
  3. }  

應用場景

這裏我們先提一個問題:給Button加一個動畫,讓Button在2秒內將寬帶從當前寬度增加到500dp,也行你會說,很簡單啊,直接用view動畫就可以實現,view動畫不是有個縮放動畫,但是你可以試試,view動畫是不支持對寬度和高度進行改變的。Button繼承自TextView,setWidth是對TextView的,所以直接對Button做setWidth是不行的。那麼要怎麼做呢?
針對上面的問題,官網api給出瞭如下的方案:
  • 給你的對象加上get和set方法,如果你有權限的話
  • 用一個類來包裝原始對象,間接提高get和set方法
  • 採用ValueAnimator,監聽動畫執行過程,實現屬性的改變

有了上面的說明,我們大致明白了,要實現開始說的這個問題的效果,我們需要用一個間接的類來實現get和set方法或者自己實現一個ValueAnimator。
第一種,自己封裝一個類實現get和set方法,這也是我們常用的,拓展性強

[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public class ViewWrapper {  
  2.  private View target;  
  3.  public ViewWrapper(View target) {  
  4.      this.target = target;  
  5.  }  
  6.  public int getWidth() {  
  7.      return target.getLayoutParams().width;  
  8.  }  
  9.  public void setWidth(int width) {  
  10.      target.getLayoutParams().width = width;  
  11.      target.requestLayout();  
  12.  }  
  13. }  

第二種,採用ValueAnimator,監聽動畫過程。

[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. private void startValueAnimator(final View target, final int start, final int end) {  
  2.    ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);  
  3.    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
  4.        private IntEvaluator mEvaluation = new IntEvaluator();//新建一個整形估值器作爲臨時變量  
  5.   
  6.        @Override  
  7.        public void onAnimationUpdate(ValueAnimator animation) {  
  8.            //獲得當前動畫的進度值 1~100之間  
  9.            int currentValue = (int) animation.getAnimatedValue();  
  10.            //獲得當前進度佔整個動畫過程的比例,浮點型,0~1之間  
  11.            float fraction = animation.getAnimatedFraction();  
  12.            //調用估值器,通過比例計算出寬度   
  13.            int targetWidth = mEvaluation.evaluate(fraction, start, end);  
  14.            target.getLayoutParams().width = targetWidth;  
  15.            //設置給作用的對象,刷新頁面  
  16.            target.requestLayout();  
  17.        }  
  18.    });  
  19. }  

屬性動畫的工作原理

屬性動畫的工作原理,主要是對作用的對象不斷的調用get/set方法來改變初始值和最終值,然後set到動畫屬性上即可。然後通過消息機制(Handler(不過這裏的Handler不是我們常用的handler,而是AnimationHandler,它其實本質就是一個Runable)和Looper去將動畫執行出來),通過代碼我們發現它調了JNI的代碼,不過這個我們不用關心,我們直接看ObjectAnimator.start()
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. private void start(boolean playBackwards) {  
  2.         if(Looper.myLooper() == null) {  
  3.             throw new AndroidRuntimeException("Animators may only be run on Looper threads");  
  4.         } else {  
  5.             this.mPlayingBackwards = playBackwards;  
  6.             this.mCurrentIteration = 0;  
  7.             this.mPlayingState = 0;  
  8.             this.mStarted = true;  
  9.             this.mStartedDelay = false;  
  10.             ((ArrayList)sPendingAnimations.get()).add(this);  
  11.             if(this.mStartDelay == 0L) {  
  12.                 this.setCurrentPlayTime(this.getCurrentPlayTime());  
  13.                 this.mPlayingState = 0;  
  14.                 this.mRunning = true;  
  15.                 if(this.mListeners != null) {  
  16.                     ArrayList animationHandler = (ArrayList)this.mListeners.clone();  
  17.                     int numListeners = animationHandler.size();  
  18.   
  19.                     for(int i = 0; i < numListeners; ++i) {  
  20.                         ((AnimatorListener)animationHandler.get(i)).onAnimationStart(this);  
  21.                     }  
  22.                 }  
  23.             }  
  24.   
  25.             ValueAnimator.AnimationHandler var5 = (ValueAnimator.AnimationHandler)sAnimationHandler.get();  
  26.             if(var5 == null) {  
  27.                 var5 = new ValueAnimator.AnimationHandler(null);  
  28.                 sAnimationHandler.set(var5);  
  29.             }  
  30.   
  31.             var5.sendEmptyMessage(0);  
  32.         }  
  33.     }  

[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. private static final ThreadLocal<ArrayList<ValueAnimator>> sAnimations = new ThreadLocal() {  
  2.        protected ArrayList<ValueAnimator> initialValue() {  
  3.            return new ArrayList();  
  4.        }  
  5.    };  
  6.    private static final ThreadLocal<ArrayList<ValueAnimator>> sPendingAnimations = new ThreadLocal() {  
  7.        protected ArrayList<ValueAnimator> initialValue() {  
  8.            return new ArrayList();  
  9.        }  
  10.    };  
  11.    private static final ThreadLocal<ArrayList<ValueAnimator>> sDelayedAnims = new ThreadLocal() {  
  12.        protected ArrayList<ValueAnimator> initialValue() {  
  13.            return new ArrayList();  
  14.        }  
  15.    };  
  16.    private static final ThreadLocal<ArrayList<ValueAnimator>> sEndingAnims = new ThreadLocal() {  
  17.        protected ArrayList<ValueAnimator> initialValue() {  
  18.            return new ArrayList();  
  19.        }  
  20.    };  
  21.    private static final ThreadLocal<ArrayList<ValueAnimator>> sReadyAnims = new ThreadLocal() {  
  22.        protected ArrayList<ValueAnimator> initialValue() {  
  23.            return new ArrayList();  
  24.        }  
  25.    };  

這裏系統怎麼計算每一幀的動畫的呢,看看下面的代碼
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. void animateValue(float fraction) {  
  2.         fraction = this.mInterpolator.getInterpolation(fraction);  
  3.         this.mCurrentFraction = fraction;  
  4.         int numValues = this.mValues.length;  
  5.   
  6.         int numListeners;  
  7.         for(numListeners = 0; numListeners < numValues; ++numListeners) {  
  8.             this.mValues[numListeners].calculateValue(fraction);  
  9.         }  
  10.   
  11.         if(this.mUpdateListeners != null) {  
  12.             numListeners = this.mUpdateListeners.size();  
  13.   
  14.             for(int i = 0; i < numListeners; ++i) {  
  15.                 ((ValueAnimator.AnimatorUpdateListener)this.mUpdateListeners.get(i)).onAnimationUpdate(this);  
  16.             }  
  17.         }  
  18.   
  19.     }  

不過我們知道要改變動畫,一定調用了get/set方法,那我們重點看下這相關的代碼。這段代碼在setProperty方法裏面
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public void setProperty(Property property) {  
  2.        if(this.mValues != null) {  
  3.            PropertyValuesHolder valuesHolder = this.mValues[0];  
  4.            String oldName = valuesHolder.getPropertyName();  
  5.            valuesHolder.setProperty(property);  
  6.            this.mValuesMap.remove(oldName);  
  7.            this.mValuesMap.put(this.mPropertyName, valuesHolder);  
  8.        }  
  9.   
  10.        if(this.mProperty != null) {  
  11.            this.mPropertyName = property.getName();  
  12.        }  
  13.   
  14.        this.mProperty = property;  
  15.        this.mInitialized = false;  
  16.    }  
這裏有一個PropertyValuesHolder,顧名思義這是一個操作數據的類,和我們的adapter的Holder差不多,該方法的get方法主要用到了反射。
[html] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. private void setupValue(Object target, Keyframe kf) {  
  2.        if(this.mProperty != null) {  
  3.            kf.setValue(this.mProperty.get(target));  
  4.        }  
  5.   
  6.        try {  
  7.            if(this.mGetter == null) {  
  8.                Class e = target.getClass();  
  9.                this.setupGetter(e);  
  10.            }  
  11.   
  12.            kf.setValue(this.mGetter.invoke(target, new Object[0]));  
  13.        } catch (InvocationTargetException var4) {  
  14.            Log.e("PropertyValuesHolder", var4.toString());  
  15.        } catch (IllegalAccessException var5) {  
  16.            Log.e("PropertyValuesHolder", var5.toString());  
  17.        }  
  18.   
  19.    }  

代碼就看到這,有興趣的可以去看下源碼



使用屬性動畫需要注意的事項

使用幀動畫時,當圖片數量較多且圖片分辨率較大的時候容易出現OOM,需注意,儘量避免使用幀動畫。 使用無限循環的屬性動畫時,在Activity退出時即使停止,否則將導致Activity無法釋放從而造成內存泄露。 View動畫是對View的影像做動畫,並不是真正的改變了View的狀態,因此有時候會出現動畫完成後View無法隱藏(setVisibility(View.GONE)失效),這時候調用view.clearAnimation()清理View動畫即可解決。 不要使用px,使用px會導致不同設備上有不同的效果。 View動畫是對View的影像做動畫,View的真實位置沒有變動,也就導致點擊View動畫後的位置觸摸事件不會響應,屬性動畫不存在這個問題。 使用動畫的過程中,使用硬件加速可以提高動畫的流暢度。 動畫在3.0以下的系統存在兼容性問題,特殊場景可能無法正常工作,需做好適配工作。

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