Android動畫常用格式和注意事項(xiàng)總結(jié)

引言:這篇文章簡單介紹一下Android動畫的基本寫法和一些要注意的地方,幫助大家更加容易使用Android動畫。由于網(wǎng)上有太多更加詳細(xì)、篇幅更大的文章,這里不做太多太詳細(xì)的解釋性的內(nèi)容,因?yàn)閷τ贏ndroid動畫,難點(diǎn)在于:自定義View動畫、自定義屬性動畫集、差值器、估值器,而這幾個知識點(diǎn)既是一時半會說不清楚的(用到物理知識等方面),又是平常實(shí)際開發(fā)中很少用到的(很多創(chuàng)業(yè)app注重實(shí)用性和開發(fā)成本,所以很少會用到大量的花俏動效,這里指“創(chuàng)業(yè)App”)


一、View動畫

  1. 系統(tǒng)View動畫
    gif:
(1) xml形式
  * 首先在/res/anim目錄下新建xxx.xml文件:


* 然后,調(diào)用這個動畫,并賦予指定view:
animation = AnimationUtils.loadAnimation(getActivity(),R.anim.view_anim); getImageView().startAnimation(animation);
注意:
android:zAdjustment:允許在動畫播放期間,調(diào)整播放內(nèi)容在Z軸方向的順序,normal(0):正在播放的動畫內(nèi)容保持當(dāng)前的Z軸順序,
top(1):在動畫播放期間,強(qiáng)制把當(dāng)前播放的內(nèi)容放到其他內(nèi)容的上面;
bottom(-1):在動畫播放期間,強(qiáng)制把當(dāng)前播放的內(nèi)容放到其他內(nèi)容之下

(2) Java形式
  * 首先,創(chuàng)建一個你想要的Animation(TranslateXX,ScaleXX,RotateXX,AlphaXX),然后開始動畫
    ```

animation = new RotateAnimation(0, +720, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(3000);
animation.setInterpolator(getActivity(),android.R.anim.accelerate_decelerate_interpolator);
getImageView().startAnimation(animation);
```

  * 雖然達(dá)不到xml方式的那種各種動畫效果疊加同時進(jìn)行的效果,我們依然可以使用`AnimationListener`來將幾個動畫連接起來:
    ```

animation.setAnimationListener(this);
……
int i =4;
@Override
public void onAnimationEnd(Animation animation) {
i-=1;
switch (i){
case 3:
animation = new TranslateAnimation(0,0,0,-160);
animation.setDuration(2000);
animation.setInterpolator(getActivity(),android.R.anim.accelerate_decelerate_interpolator);
/**
* 這里,注意點(diǎn):
* 1.setFillAfter 表示的是:是否讓畫面停留在最后一刻的狀態(tài)
*/
animation.setFillAfter(true);
animation.setAnimationListener(this);
getImageView().startAnimation(animation);
break;
case 2:
//要自己實(shí)際移動View的坐標(biāo)位置
getImageView().offsetTopAndBottom(-160);
// animation.cancel();
getImageView().clearAnimation();
getImageView().invalidate();
animation = new ScaleAnimation(1f,1.5f,1f,1.5f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animation.setDuration(2000);
animation.setInterpolator(getActivity(),android.R.anim.accelerate_decelerate_interpolator);
animation.setFillAfter(true);
animation.setAnimationListener(this);
getImageView().startAnimation(animation);
break;
case 1:
// animation.cancel();
getImageView().clearAnimation();
getImageView().invalidate();
animation = new AlphaAnimation(1.0f,0.0f);
animation.setDuration(1000);
animation.setInterpolator(getActivity(),android.R.anim.accelerate_decelerate_interpolator);
animation.setFillAfter(true);
animation.setAnimationListener(this);
getImageView().startAnimation(animation);
break;
default:
getImageView().clearAnimation();
this.dismiss();
break;
}
}
```

  1. 自定義View動畫
    如果不滿足于系統(tǒng)的幾種動畫,那么可以結(jié)合自己的物理知識,利用android.graphics.Cameraandroid.graphics.Matrix類,去實(shí)現(xiàn)自定義的動畫效果。要實(shí)現(xiàn)自定義Animation,那么,就需要繼承Animation類。下面是一個示例:
* gif:![](http://upload-images.jianshu.io/upload_images/2369895-f634c7e8714b86d0.gif?imageMogr2/auto-orient/strip)
* 代碼:
  ```
  public class CustomViewAnim extends Animation {
      private Camera mCamera;
      private final float mFromDegrees;
      private final float mToDegrees;
      private final float mCenterX;
      private final float mCenterY;
      private final float mDepthZ;
      private final boolean mReverse;
      public CustomViewAnim(float mFromDegrees, float mToDegrees
           , float mCenterX, float mCenterY, float mDepthZ, boolean mReverse) {
          this.mFromDegrees = mFromDegrees;
          this.mToDegrees = mToDegrees;
          this.mCenterX = mCenterX;
          this.mCenterY = mCenterY;
          this.mDepthZ = mDepthZ;
          this.mReverse = mReverse;
      }
      /**
       * 做一些初始化工作
       */
      @Override
      public void initialize(int width, int height, int parentWidth, int parentHeight) {
          super.initialize(width, height, parentWidth, parentHeight);
          mCamera = new Camera();
      }
      /**
       * 相應(yīng)的矩陣變換
       */
      @Override
      protected void applyTransformation(float interpolatedTime, Transformation t) {
          super.applyTransformation(interpolatedTime, t);
          final float fromDegrees = mFromDegrees;
          float degrees = fromDegrees + ((mToDegrees - fromDegrees)) * interpolatedTime;
          final float centerX = mCenterX;
          final float centerY = mCenterY;
          final Camera camera = mCamera;
          final Matrix matrix = t.getMatrix();
          camera.save();
          if (mReverse){
              camera.translate(0f,0f,mDepthZ * interpolatedTime);
          }else{
              camera.translate(0f,0f,mDepthZ*(1f - interpolatedTime));
          }
          camera.rotateY(degrees);
          camera.getMatrix(matrix);
          camera.restore();
          matrix.preTranslate(-centerX,-centerY);
          matrix.postTranslate(centerX,centerY);
      }
  }
  ```
  1. LayoutAnimation
    給ViewGroup中的所有子View加載設(shè)定的動畫,是View動畫的特殊應(yīng)用。
* gif:![](http://upload-images.jianshu.io/upload_images/2369895-af88f598a7d6e493.gif?imageMogr2/auto-orient/strip)
* 步驟:
  * 1.定義`/res/anim/view_anim.xml`動畫文件:和view動畫的xml形式一樣。
  * 2.在Java代碼中給根布局設(shè)定屬性:
    ```
  container = (LinearLayout) findViewById(R.id.list_container);
    /**
     * 設(shè)置LayoutAnimation
     */
    Animation animation = AnimationUtils.loadAnimation(this,
     R.anim.view_anim);
    LayoutAnimationController controller = new LayoutAnimationController(animation);
    controller.setDelay(0.6f);///表示每個子View加載的延時為`view_anim.xml`的duration時間的60%.
    controller.setOrder(LayoutAnimationController.ORDER_NORMAL);///表示按加載順序加載每個子view
    container.setLayoutAnimation(controller);////設(shè)置好
    ```
  1. Activity和Fragment跳轉(zhuǎn)動畫
* Activity跳轉(zhuǎn)動畫
  在**startActivity()方法之后** 或者 **finish()/super.onBackPressed()方法之后**執(zhí)行:
  `overridePendingTransition(R.anim.enter_anim,R.anim.exit_anim);`
  其中,R.anim.enter_anim 和 R.anim.exit_anim 的格式與View動畫的格式一樣。
* Fragment加載動畫(API11即Android3.0之后可用)
  * gif:
  * **FragmentTransaction.setTransition:**如下示例代碼,使用系統(tǒng)默認(rèn)的Fragment加載動畫。
  * **FragmentTransaction.setCustomAnimations:**如下示例代碼,使用自定義的View動畫加載。
  * 注意,這種方式對于DialogFragment是無效的,因?yàn)镈ialogFragment沒有對應(yīng)的父容器,本身就是固定的。
  * 示例代碼:
  定義`fragment_enter_anim.xml`文件(`fragment_exit_anim.xml`文件類似):
  ```

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:interpolator="@android:anim/linear_interpolator"
android:zAdjustment="normal"
android:shareInterpolator="true"
android:repeatMode="restart"
android:duration="1000"
>
<translate
android:fromYDelta="-600"
android:toYDelta="0"
android:startOffset="500"
/>
<rotate
android:fromDegrees="-90"
android:toDegrees="0"
android:pivotY="50%"
android:pivotX="50%"
/>
<alpha
android:fromAlpha="0.5"
android:toAlpha="1"
/>
</set>
然后,java代碼中給FragmentTransaction去加載。
private void addFragment() {
Fragment fragment = new TestFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
// transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
// transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
transaction.setCustomAnimations(R.anim.fragment_enter_anim,R.anim.fragment_exit_anim);
transaction.add(R.id.frame_root,fragment);
// transaction.addToBackStack("frag");
transaction.commit();
}
```
* gif:

二、 幀動畫

  • gif:


  • 首先,在/res/drawable目錄下創(chuàng)建xml文件:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/img1" android:duration="500"/>
<item android:drawable="@drawable/img2" android:duration="500"/>
<item android:drawable="@drawable/img3" android:duration="500"/>
</animation-list>

* 然后讓指定view去設(shè)置`background`,并讓動畫`start`

getImageView().setBackgroundResource(R.drawable.frame_anim);
AnimationDrawable ad = (AnimationDrawable) getImageView().getBackground();
ad.start();


## 三、屬性動畫
gif:
![](http://upload-images.jianshu.io/upload_images/2369895-7fa4e8eb9d8fd9a4.gif?imageMogr2/auto-orient/strip)

(1) xml形式
> 注意:這種形式下,動畫效果容易失效!建議使用Java代碼方式動態(tài)加載動畫。

這里,使用的是/res/animator/xxx.xml文件,而不是/res/anim/xxx.xml,java中的寫法是:`R.animator.xxx`而不是`R.anim.xxx`
首先,定義xml屬性:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together"
>

<objectAnimator
android:propertyName="string"
android:duration="1000"
android:valueFrom="@android:color/white"
android:valueTo="@android:color/holo_green_dark"
android:startOffset="500"
android:repeatCount="1"
android:repeatMode="restart"
android:valueType="intType"/>
<animator
android:duration="1000"
android:valueFrom="@android:color/white"
android:valueTo="@android:color/holo_green_dark"
android:startOffset="10"
android:repeatCount="1"
android:repeatMode="restart"
android:valueType="intType"/>
</set>

然后,在java中指定對象并加載屬性動畫:

getImageView().setImageResource(R.drawable.img1);
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(getActivity(), R.animator.property_anim);
set.setTarget(getImageView());
set.start();

(2) Java形式

getImageView().setImageResource(R.drawable.img1);///設(shè)置圖片內(nèi)容
AnimatorSet set = new AnimatorSet();///創(chuàng)建屬性動畫集
set.playTogether(///“playTogether”表示所有的子動畫同時運(yùn)作
ObjectAnimator.ofFloat(getImageView(),"rotationX",0,360),
ObjectAnimator.ofFloat(getImageView(),"rotationY",0,180),
ObjectAnimator.ofFloat(getImageView(),"rotation",0,-90),
ObjectAnimator.ofFloat(getImageView(),"translationX",0,90),
ObjectAnimator.ofFloat(getImageView(),"translationY",0,90),
ObjectAnimator.ofFloat(getImageView(),"scaleX",1,1.5f),
ObjectAnimator.ofFloat(getImageView(),"scaleY",1,0.5f),
ObjectAnimator.ofFloat(getImageView(),"alpha",1,0.25f,1)
);
set.setDuration(5*1000).start();


## 四、注意事件
1. **關(guān)于Android Animation的setFillBefore、setFillAfter和setFillEnable:**
  * 如果是獨(dú)立的Animation,只有setFillAfter有效,設(shè)置為true動畫結(jié)束后保持最后的狀態(tài)。
  * 如果是AnimationSet中的Animation,因?yàn)锳nimation的作用周期可能位于整個AnimationSet動畫周期的中間一部分,setFillBefore設(shè)置的是在這個動畫被執(zhí)行前是否啟用這個動畫的第一幀效果填充開始前的動畫,setFillAfter設(shè)置的是在這個動畫結(jié)束后是否保留這個動畫的最后一幀的效果填充后面的動畫,而這兩個設(shè)置必須同時設(shè)置setFillEnable。
  * 如果想這個AnimationSet結(jié)束后保留最后的結(jié)果,需要設(shè)置AnimationSet的setFillAfter。
  > 補(bǔ)充:當(dāng)setFillEnable為false時,通過查看源碼可知在AnimationSet中自身的動畫周期不受setFillBefore和setFillAfter控制;當(dāng)Animation獨(dú)立存在時,或AnimationSet的setFillAfter為true時,ViewGroup會讀取getFillAfter值,如果為true,不clearAnimation,也就保持了最終的狀態(tài)。
2. **讓view自轉(zhuǎn):**
  * 方式一:java代碼動態(tài)設(shè)定旋轉(zhuǎn)動畫的軸心為中心:
    `animation = new RotateAnimation(0, +720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);`
  * 方式二:xml中設(shè)置`<rotate>`屬性中`pivotY="50%"`和`pivotX="50%"`來設(shè)定軸心為中點(diǎn):
    ```
<rotate
  android:fromDegrees="-90"
  android:toDegrees="0"
  android:pivotY="50%"
  android:pivotX="50%"
  />
    ```
3. **OOM問題:**
  主要出現(xiàn)在幀動畫當(dāng)中,當(dāng)圖片數(shù)量較多并且圖片較大時就容易出現(xiàn)OOM(App擁有的內(nèi)存已經(jīng)裝不下圖片了)。所以,盡量避免使用幀動畫。
4. **內(nèi)存泄露:**
  屬性動畫中有一類無線循環(huán)動畫,這類動畫需要在Activity退出時及時停止,否則將導(dǎo)致Activity無法釋放從而造成內(nèi)存泄露(ML)。通過驗(yàn)證發(fā)現(xiàn)View動畫不存在此問題。
5. **兼容性問題:**
  動畫在Android3.0(API 11)以下的系統(tǒng)上有兼容性問題,所以,需要適當(dāng)使用兼容庫如nineoldandroids等進(jìn)行適配(目前大多Android手機(jī)版本在4.0以上,這一點(diǎn)問題不大)
6. **View動畫的問題:**
  View動畫是對View的影像做動畫,不是真的改變View的布局屬性和狀態(tài),所以,有時會出現(xiàn):動畫完成后View無法隱藏的情況,即`setVisibility(View.GONE);`失效。解決方法:調(diào)用`view.clearAnimation()`清除View動畫即可。
7. **不要使用px:**
  由于不同尺寸的手機(jī),dpi不一樣,所以,用dp設(shè)定動畫更容易適配不同手機(jī)。下面分享個人的一個dp轉(zhuǎn)px的工具類DiaplayUtil:
  ```
/**
* 屏幕顯示相關(guān)工具類
* (px ,sp, dp 等轉(zhuǎn)換)
*/
public class DisplayUtil {
  private static Map<ScreenEnum,Integer> screenMap = new HashMap<>();
  /**
   * 將px值轉(zhuǎn)換為dip或dp值,保證尺寸大小不變
   *
   * @param pxValue
   * @return
   */
  public static int px2dip(float pxValue, Activity activity) {
      return (int) (pxValue / getScreenMsg(activity).get(ScreenEnum.Density) + 0.5f);
  }
  /**
   * 將dip或dp值轉(zhuǎn)換為px值,保證尺寸大小不變
   * @return
   */
  public static int dip2px(float dipValue, Activity activity) {
      return (int) (dipValue * getScreenMsg(activity).get(ScreenEnum.Density) + 0.5f);
  }
  /**
   * 將px值轉(zhuǎn)換為sp值,保證文字大小不變
   *
   * @return
   */
  public static int px2sp(float pxValue, Activity activity) {
      return (int) (pxValue / getScreenMsg(activity).get(ScreenEnum.ScaledDensity) + 0.5f);
  }
  /**
   * 將sp值轉(zhuǎn)換為px值,保證文字大小不變
   * @return
   */
  public static int sp2px(float spValue, Activity activity) {
      return (int) (spValue * getScreenMsg(activity).get(ScreenEnum.ScaledDensity) + 0.5f);
  }
  /**
   * 獲取屏幕尺寸等信息
   */
  public static Map<ScreenEnum,Integer> getScreenMsg(Activity activity){
      DisplayMetrics metric = new DisplayMetrics();
      activity.getWindowManager().getDefaultDisplay().getMetrics(metric);
      int width = metric.widthPixels;
      int height = metric.heightPixels;
      float density = metric.density;///屏幕密度(0.75, 1.0 . 1.5)
      int densityDpi = metric.densityDpi;///屏幕密度DPI(120/160/240/320/480)
      float scaledDensity = metric.scaledDensity;
      if (screenMap==null) screenMap = new HashMap<>();

      screenMap.clear();
      screenMap.put(ScreenEnum.Width,width);
      screenMap.put(ScreenEnum.Height,height);
      screenMap.put(ScreenEnum.Density,(int)density);
      screenMap.put(ScreenEnum.DendityDpi,densityDpi);
      screenMap.put(ScreenEnum.ScaledDensity, (int)scaledDensity);
      return screenMap;
  }
  enum ScreenEnum{
      Width,Height,Density,DendityDpi,ScaledDensity
  }
}
  ```
8. **動畫元素的交互:**
  這里就需要和TranslateAnimation位移動畫一起講解了:由于位移動畫只是動畫過程中移動view的影像,不是實(shí)際的view布局位置的移動,所以,當(dāng)view擁有點(diǎn)擊事件時,位移動畫完成后:view的點(diǎn)擊位置和view的實(shí)際位置會不一致(3.0以前:View動畫和屬性動畫都一樣,新位置無法點(diǎn)擊,舊位置可以點(diǎn)擊;3.0及以后:屬性動畫的單擊事件觸發(fā)位置為移動后的位置,但是View動畫仍在原位置。)
  <u>解決方法</u>:改變view的布局屬性,讓view在動畫結(jié)束時實(shí)際移動。
  ```
animation = new TranslateAnimation(0,0,0,-160);
animation.setDuration(2000);
animation.setInterpolator(getActivity(),android.R.anim.accelerate_decelerate_interpolator);
animation.setFillAfter(true);
animation.setAnimationListener(this);
getImageView().startAnimation(animation);
…………
@Override
public void onAnimationEnd(Animation animation) {
        //改變view的布局屬性,讓view在動畫結(jié)束時實(shí)際移動
        getImageView().offsetTopAndBottom(-160);
}
  ```
9. **硬件加速:**
  建議在<u>頻繁使用到動畫的地方</u>使用硬件加速。
  Android從3.0(API11)開始,在繪制View的時候支持硬件加速,充分利用GPU的特性,使得繪制更加平滑,但是會多消耗一些內(nèi)存。
  * Application級別:`<applicationandroid:hardwareAccelerated="true" ...>`
  * Activity級別:`<activity android:hardwareAccelerated="false" ...>`
  * Window級別:

getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

     注意: 目前為止,Android還不支持在Window級別關(guān)閉硬件加速.
   * View級別:`myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);`
     注意:目前為止,Android還不支持在View級別開啟硬件加速。
   * 檢測是否啟用了硬件加速:

// 方法一
// 此方法返回true,如果myView掛在一個開啟了硬件加速的Window之下,
// 也就是說,它在繪制的時候不一定使用了硬件加速,getDrawingCache
myView.isHardwareAccelerated();
// 方法二
// 返回true,如果canvas在繪制的時候啟用了硬件加速
// 盡量采用此方法來判斷是否開啟了硬件加速
canvas.isHardwareAccelerated();


   * 硬件加速可能出現(xiàn)的限制:
      1.某些UI元素沒有顯示:可能是沒有調(diào)用invalidate
      2.某些UI元素沒有更新:可能是沒有調(diào)用invalidate
      3.繪制不正確:可能使用了不支持硬件加速的操作, 需要關(guān)閉硬件加速或者繞過該操作
      4.拋出異常:可能使用了不支持硬件加速的操作, 需要關(guān)閉硬件加速或者繞過該操作
---
## Demo與推薦文章
  本文中用到的一些相關(guān)的R.anim.xxx文件可能沒有詳細(xì)說明代碼,可下載Demo
Demo地址:https://github.com/androidjp/anim-demo
Android動畫推薦文章(想了解更詳細(xì)可以看看):
http://www.cnblogs.com/ldq2016/p/5407061.html
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,287評論 25 708
  • 1 背景 不能只分析源碼呀,分析的同時也要整理歸納基礎(chǔ)知識,剛好有人微博私信讓全面說說Android的動畫,所以今...
    未聞椛洺閱讀 2,746評論 0 10
  • Animation Animation類是所有動畫(scale、alpha、translate、rotate)的基...
    四月一號閱讀 1,936評論 0 10
  • list.tmall.hk list.tmall.com s.m.tmall.com www.tmall.com ...
    測試一下下閱讀 1,284評論 0 0
  • 1、tab-digit一個數(shù)字計時翻轉(zhuǎn)效果 2、easy-video-player視頻播放器 3、VideoLis...
    芝諾龜閱讀 360評論 1 1