Android動畫深入分析

導語

本章學習內容:介紹View動畫和自定義View動畫,View動畫一些特殊的使用場景,對屬性動畫全面性的介紹,使用動畫的一些注意事項。

主要內容

  • View動畫
  • View動畫的特殊使用場景
  • 屬性動畫
  • 使用動畫的注意事項

具體內容

View動畫

View動畫的作用對象是View,支持四種動畫效果:

  • 平移
  • 縮放
  • 旋轉
  • 透明
View動畫的種類

上述四種變換效果對應着Animation四個子類: TranslateAnimation 、 ScaleAnimation 、 RotateAnimation 和 AlphaAnimation 。這四種動畫皆可以通過XML定義,也可以通過代碼來動態創建。

名稱 標籤 子類 效果
平移動畫 <translate> TranslateAnimation 移動View
縮放動畫 <scale> ScaleAnimation 放大或縮小View
旋轉動畫 <rotate> RotateAnimation 旋轉View
透明度動畫 <alpha> AlphaAnimation 改變View的透明度

xml定義動畫:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
  //動畫插值器,影響動畫的播放速度
android:interpolator="@android:anim/accelerate_interpolator"
  //表示集合中的動畫是否和集合共享一個插值器
android:shareInterpolator="true" >
//透明度動畫,對應 AlphaAnimation 類,可以改變 View 的透明度
  <alpha
        android:duration="3000"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
          //旋轉動畫,對應着 RotateAnimation ,它可以使 View 具有旋轉的動畫效果
    <rotate
        android:duration="2000"
        android:fromDegrees="0"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:startOffset="3000"
        android:toDegrees="180" />
       <!--通過設置第一個alpha動畫播放3s後啓動rotate動畫實現組合動畫,如果不設置startOffset則同時播放
    pivotX:表示旋轉時候的相對軸的座標點,即圍繞哪一點進行旋轉,默認情況下軸點是 View 中心
    -->
      //平移動畫,對應 TranslateAnimation 類,可以使 View 完成垂直或者水平方向的移動效果。
    <translate
        android:fromXDelta="500"
        android:toXDelta="0" />
          //縮放動畫,對應 ScaleAnimation 類,可以使 View 具有放大和縮小的動畫效果。
    <scale
        android:duration="1000"
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:pivotX="50"
        android:pivotY="50"
        android:toXScale="2"
        android:toYScale="2" />
    </set>
  1. 標籤表示動畫集合,對應AnimationSet類,可以包含一個或若干個動畫,內部還可以嵌套其他動畫集合。
    1. android:interpolator 表示動畫集合所採用的插值器,插值器影響動畫速度,比如非勻速動畫就需要通過插值器來控制動畫的播放過程。
    2. android:shareInterpolator 表示集合中的動畫是否和集合共享同一個插值器,如果集合不指定插值器,那麼子動畫就需要單獨指定所需的插值器或默認值。
  2. <translate>、<scale>、 <rotate> 、 <alpha> 這幾個子標籤分別代表四種變換效果。
  3. android:fillAfter屬性表示動畫結束以後, View 是否停留在結束動畫的位置,如果爲 false , View 會回到動畫開始的位置。這個參數在動畫 XML 文件的 </set> 節點中設置或在程序 Java 代碼中進行設置:setFillAfter(true)。
  4. 定義完View動畫的xml後,通過以下代碼應用動畫:
 Animation anim = AnimationUtils.loadAnimation(context,R.anim.animation_test);
 view.startAnimation(anim);

代碼動態創建動畫:

public static interface AnimationListener {
    void onAnimationStart(Animation animation);
    void onAnimationEnd(Animation animation);
    void onAnimationRepeat(Animation animation);
}
自定義View動畫

除了系統提供的四種動畫外,我們可以根據需求自定義動畫,自定義一個新的動畫只需要繼承 Animation 這個抽象類,然後重寫它的 inatialize 和 applyTransformation 這兩個方法,在 initialize 方法中做一些初始化工作,在 Transformation 方法中進行矩陣變換即可,很多時候纔有 Camera 來簡化矩陣的變換過程,其實自定義動畫的主要過程就是矩陣變換的過程,矩陣變換是數學上的概念,需要掌握該方面知識方能輕鬆實現自定義動畫,例子可以參考 Android 的 APIDemos 中的一個自定義動畫 Rotate3dAnimation ,這是一個可以圍繞 Y 軸旋轉並同時沿着 Z 軸平移從而實現類似一種 3D 效果的動畫。

幀動畫

幀動畫是順序播放一組預先定義好的圖片,使用簡單,但容易引起OOM,所以在使用幀動畫時應儘量避免使用過多尺寸較大的圖片。系統提供了另一個類 AnimationDrawble 來使用幀動畫,使用的時候,需要通過 XML 定義一個 AnimationDrawble ,如下:

//\res\drawable\frame_animation_list.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
    根標籤爲 animation-list,其中 oneshot 代表着是否只展示一遍,設置爲 false 會不停的循環播放動畫
    根標籤下,通過 item 標籤對動畫中的每一個圖片進行聲明
    android:duration 表示展示所用的該圖片的時間長度
 -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/one"
        android:duration="2000"/>
    <item
        android:drawable="@drawable/two"
        android:duration="2000"/>
    <item
        android:drawable="@drawable/three"
        android:duration="2000"/>
</animation-list

View動畫的特殊使用場景

View 動畫除了可以實現的四種基本的動畫效果外,還可以在一些特殊的場景下使用,比如在 ViewGroup 中可以控制子元素的出場效果,在 Activity 中可以實現不同 Activity 之間的切換效果。

LayoutAnimation

作用於ViewGroup,爲ViewGroup指定一個動畫,當它的子元素出場時都會具有這種動畫效
果,一般用在ListView上。

//res/anim/layout_animation.xml
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.5"
    android:animationOrder="normal"
    android:animation="@anim/zoom_in">
</layoutAnimation>
  • android:delay
    表示子元素開始動畫的延時時間,取值爲子元素入場動畫時間 duration 的倍數,比如子元素入場動畫時間週期爲 300ms ,那麼 0.5 表示每個子元素都需要延遲 150ms 才能播放入場動畫,即第一個子元素延遲 150ms 開始播放入場動畫,第二個子元素延遲 300ms 開始播放入場動畫,依次類推進行。
  • android:animationOrder
    表示子元素動畫的開場順序,normal(正序)、reverse(倒序)、random(隨機)。
  • 爲 ViewGroup 指定屬性
    android:layoutAnimation="@anim/layout_animation"。

通過 LayoutAnimationController 來實現:

//用於控制子 view 動畫效果
LayoutAnimationController layoutAnimationController= new LayoutAnimationController(AnimationUtils.loadAnimation(this,R.anim.zoom_in));
layoutAnimationController.setOrder(LayoutAnimationController.ORDER_NORMAL);
listView.setLayoutAnimation(layoutAnimationController);
listView.startLayoutAnimation();
Activity的切換效果

我們可以自定義Activity的切換效果,主要通過overridePendingTransition(int enterAnim , int exitAnim) 方法。該方法必須要在 startActivity(intent) 和 finish() 方法之後調用纔會有效。

//啓動新的Activity帶動畫
Intent intent=new Intent(MainActivity.this,Main2Activity.class);
startActivity(intent);
overridePendingTransition(R.anim.zoom_in,R.anim.zoom_out);
//退出Activity本身帶動畫
@Override
public void finish() {
    super.finish();
    overridePendingTransition(R.anim.zoom_in,R.anim.zoom_out);
}

Fragment 也可以添加切換動畫,通過 FragmentTransation 中的 setCustomAnimations() 方法來實現切換動畫,這個動畫需要的是 View 動畫,不能使用屬性動畫,因爲屬性動畫也是 API11 才引入的,不兼容。

屬性動畫

屬性動畫是 API 11 引入的新特性,屬性動畫可以對任何對象做動畫,甚至還可以沒有對象。可以在一個時間間隔內完成對象從一個屬性值到另一個屬性值的改變。與View動畫相比,屬性動畫幾乎無所不能,只要對象有這個屬性,它都能實現動畫效果。API11以下可以通過 nineoldandroids 庫來兼容以前版本。

屬性動畫有ValueAnimator、ObjectAnimator和AnimatorSet等概念。其中ObjectAnimator繼承自ValueAnimator,AnimatorSet是動畫集合。

舉例:

  1. 改變一個對象 TranslationY 屬性,讓其沿着 Y 軸平移一段距離:
private void translateViewByObjectAnimator(View targetView){
//TranslationY 目標 View 要改變的屬性
//ivShow.getHeight() 要移動的距離
    ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(targetView,"TranslationY",ivShow.getHeight());
    objectAnimator.start();
}
  1. 改變一個對象的背景色屬性,3秒內從0xFFFF8080到0xFF8080FF漸變,無限循環且有反轉效果:
 private void changeViewBackGroundColor(View targetView){
     ValueAnimator valueAnimator=ObjectAnimator.ofInt(targetView,"backgroundColor", Color.RED,Color.BLUE);
     valueAnimator.setDuration(3000);
     //設置估值器,該處插入顏色估值器
     valueAnimator.setEvaluator(new ArgbEvaluator());
     //無限循環
     valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
     //反轉模式
     valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
     valueAnimator.start();
 }

動畫集合,5 秒內對 View 旋轉、平移、縮放和透明度進行了改變:

 private void startAnimationSet(View targetView){
     AnimatorSet animatorSet=new AnimatorSet();
     animatorSet.playTogether(ObjectAnimator.ofFloat(targetView,"rotationX",0,360),
              //旋轉
             ObjectAnimator.ofFloat(targetView,"rotationY",0,360),
             ObjectAnimator.ofFloat(targetView,"rotation",0,-90),
              //平移
             ObjectAnimator.ofFloat(targetView,"translationX",0,90),
             ObjectAnimator.ofFloat(targetView,"translationY",0,90),
              //縮放
             ObjectAnimator.ofFloat(targetView,"scaleX",1,1.5f),
             ObjectAnimator.ofFloat(targetView,"scaleY",1,1.5f),
              //透明度
             ObjectAnimator.ofFloat(targetView,"alpha",1,0.25f,1));
             animatorSet.setDuration(3000).start();
 }

也可以通過在xml中定義在 res/animator/ 目錄下。具體如下:

 \res\animator\value_animator.xml
 <?xml version="1.0" encoding="utf-8"?><!--set 標籤對應着 AnimatorSet-->
 <set xmlns:android="http://schemas.android.com/apk/res/android"
     android:ordering="together">
     <!--對應着 ObjectAnimator-->
     <objectAnimator
         android:propertyName="x"
         android:repeatCount="infinite"
         android:repeatMode="reverse"
         android:startOffset="10"
         android:valueTo="300"
         android:valueType="floatType" />
     <!--其中propertyName 屬性設置爲translationX ,valueType 設置爲floatType 可以正常啓動
        如果 valueType 設置爲 intType 將報錯,即屬性類型必須爲 floatType 類型,並且android:propertyName="translationX" 表示移動 300 ,而  android:propertyName="x"表示移動到300 ,是兩個不同屬性-->
     <!--startOffset 指定延遲多久開始播放動畫-->
     <!--valueType 表示指定的 android:propertyName 所指定屬性的類型,intType 表示指定屬性是整型的,
      如果指定屬性爲顏色,那麼不需要指定 valueType 屬性,系統會自動處理
      repeatCount 表示動畫循環次數,默認值爲0,-1 表示無限循環-->
 
     <!--對應着 ValueAnimator-->
    <animator
     android:duration="300"
     android:valueFrom="0"
     android:valueTo="360"
     android:startOffset="10"
     android:repeatCount="infinite"
     android:repeatMode="reverse"
     android:valueType="intType"/>
 </set>

使用動畫:

 AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(context , R.animator.ani
 m);
 set.setTarget(view);
 set.start();

實際開發中建議使用代碼實現屬性動畫。很多時候一個屬性的起始值是無法提前確定的。

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

  • 估值器(類型估值算法, TypeEvaluator) 的作用是根據當前屬性改變的百分比來計算改變後的屬性值。系統預置有IntEvaluator(針對整型屬性) 、FloatEvaluator(浮點型屬性) 、ArgbEvaluator(針對 Color 屬性)。

如圖所示,表示一個勻速動畫,採用了線性插值器和整型估值算法,在 40ms 內,View 的 X 屬性實現了從 0 到 40 的變化。

屬性動畫要求對象的該屬性有 set 和 get(可選) 方法,插值器和估值算法除了系統提供的外,我們還可以自己定義,插值器或者估值算法都是一個接口,且內部只有一個方法,我們只需要派生一個類實現該接口即可,然後就可以做出千變萬化的動畫效果了。具體而言是:自定義插值器需要實現 Interpolator 或者 TimeInterpolator ,自定義估值算法需要實現 TypeEvaluator 。如果要對其他類型(非int,float,color)做動畫,必須要自定義類型估值算法。

屬性動畫的監聽器

屬性動畫提供了監聽器用於監聽動畫的播放過程,主要有如下兩個接口:AnimatorUpdateListener 和 AnimatorListener 。

public static interface AnimatorListener {
void onAnimationStart(Animator animation); //動畫開始
void onAnimationEnd(Animator animation); //動畫結束
void onAnimationCancel(Animator animation); //動畫取消
void onAnimationRepeat(Animator animation); //動畫重複播放
}

爲了方便開發,系統提供了AnimatorListenerAdapter類,它是AnimatorListener的適配器類,可以有選擇的實現以上4個方法。

public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
對任意屬性做動畫

屬性動畫原理:屬性動畫要求動畫作用的對象提供 get 方法和 set 方法,屬性動畫根據外界傳遞該屬性的初始值和最終值以動畫的效果去多次調用 set 方法,每次傳遞給 set 方法的值都不一樣,確切的來說是隨着時間的推移,所傳遞的值越來越接近最終值。總結一下,我們對 object 對象屬性 abc 做動畫,如果想要動畫生效,要同時滿足兩個條件:

  • object 必須要提供 setAbc() 方法,如果動畫的時候沒有傳遞初始值,那麼還要提供 getAbc() 方法,因爲系統要去取 abc 屬性的初始值(如果這條不滿足,程序直接crash)。
  • object 的 setAbc() 對屬性 abc 所做的改變必須能夠通過某種方法反應出來(即最終體現了 UI 的變化),比如會帶來 UI 的改變之類(如果這條不滿足,動畫無效果,但是程序不會crash)。

我們給 Button 的 width 屬性做動畫無效果但是沒有crash的原因就是 Button 內部提供了 setWidth 和 getWidth 方法,但是這個 setWidth 方法並不是改變 UI 大小的,而是用來設置最大寬度和最小寬度的。對於上面屬性動畫的兩個條件來說,這個例子只滿足了條件 1 而未滿足條件 2。

針對上面問題,官方文檔給出了 3 種解決方法:

  • 請給你的對象加上get和set方法,如果你有權限的話
    對於SDK或者其他第三方類庫的類無法加上的。
  • 用一個類來包裝原始對象,間接爲其提供get和set方法:
 /**
  * 將 Button 沿着 X 軸方向放大
  * @param button
  */
 private void performAnimationByWrapper(View button){
     ViewWrapper viewWrapper=new ViewWrapper(button);
     ObjectAnimator.ofInt(viewWrapper,"width",800)
             .setDuration(5000)
             .start();
 }
  private class ViewWrapper {
         private View targetView;
         public ViewWrapper(View targetView) {
             this.targetView = targetView;
         }
         public int getWidth() {
             //注意調用此函數能得到 View 的寬度的前提是, View 的寬度是精準測量模式,即不可以是 wrap_content
             //否則得不到正確的測量值
             return targetView.getLayoutParams().width;
         }
         public void setWidth(int width) {
           //重寫設置目標 view 的佈局參數,使其改變大小
             targetView.getLayoutParams().width = width;
           //view 大小改變需要調用重新佈局
             targetView.requestLayout();
         }
     }
  • 採用ValueAnimator,監聽動畫過程,自己實現屬性的改變
    ValueAnimator 本身不作用於任何對象,也就是說直接使用它沒有任何動畫效果(所以系統提供了它的子類 ObjectAnimator 供我們直接使用,作用於對象直接執行動畫效果,而 ValueAnimator 只是提供改變一個值的過程,並能監聽到整個值的改變過程,我們基於這個過程可以自己去實現動畫效果,在這個過程中做想要達到的效果,自己去實現)。它可以對一個值做動畫,然後我們可以監聽其動畫過程,在動畫過程中修改我們對象的屬性,這樣我們自己就實現了對對象做了動畫。
 //new 一個整型估值器,用於下面比例值計算使用(可以自己去計算,這裏直接使用系統的)
 private IntEvaluator intEvaluator = new IntEvaluator();
 private void performAnimatorByValue(final View targetView, final int start, final int end) {
     ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
     valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
             //獲取當前動畫進度值
             int currentValue = (int) animation.getAnimatedValue();
             //獲取當前進度佔整個動畫比例
             int fraction = (int) animation.getAnimatedFraction();
             //直接通過估值器根據當前比例計算當前 View 的寬度,然後設置給 View
             targetView.getLayoutParams().width = intEvaluator.evaluate(fraction, start, end);
             targetView.requestLayout();
         }
     });
     valueAnimator.setDuration(5000)
             .start();
 }
屬性動畫的工作原理

屬性動畫需要運行在有Looper的線程中,系統通過反射調用被作用對象get/set方法。

使用動畫的注意事項

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

更多內容戳這裏(整理好的各種文集)

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