Android动画常用格式和注意事项总结

引言:这篇文章简单介绍一下Android动画的基本写法和一些要注意的地方,帮助大家更加容易使用Android动画。由于网上有太多更加详细、篇幅更大的文章,这里不做太多太详细的解释性的内容,因为对于Android动画,难点在于:自定义View动画、自定义属性动画集、差值器、估值器,而这几个知识点既是一时半会说不清楚的(用到物理知识等方面),又是平常实际开发中很少用到的(很多创业app注重实用性和开发成本,所以很少会用到大量的花俏动效,这里指“创业App”)

Android动画常用格式和注意事项总结_第1张图片

一、View动画

  1. 系统View动画
    gif:
    Android动画常用格式和注意事项总结_第2张图片
(1) xml形式
  * 首先在/res/anim目录下新建xxx.xml文件:

Android动画常用格式和注意事项总结_第3张图片

* 然后,调用这个动画,并赋予指定view:
animation = AnimationUtils.loadAnimation(getActivity(),R.anim.view_anim); getImageView().startAnimation(animation);
注意:
android:zAdjustment:允许在动画播放期间,调整播放内容在Z轴方向的顺序,normal(0):正在播放的动画内容保持当前的Z轴顺序,
top(1):在动画播放期间,强制把当前播放的内容放到其他内容的上面;
bottom(-1):在动画播放期间,强制把当前播放的内容放到其他内容之下

(2) Java形式
  * 首先,创建一个你想要的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);
```

  * 虽然达不到xml方式的那种各种动画效果叠加同时进行的效果,我们依然可以使用`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);
/**
* 这里,注意点:
* 1.setFillAfter 表示的是:是否让画面停留在最后一刻的状态
*/
animation.setFillAfter(true);
animation.setAnimationListener(this);
getImageView().startAnimation(animation);
break;
case 2:
//要自己实际移动View的坐标位置
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动画
    如果不满足于系统的几种动画,那么可以结合自己的物理知识,利用android.graphics.Cameraandroid.graphics.Matrix类,去实现自定义的动画效果。要实现自定义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();
      }
      /**
       * 相应的矩阵变换
       */
      @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加载设定的动画,是View动画的特殊应用。
* 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代码中给根布局设定属性:
    ```
  container = (LinearLayout) findViewById(R.id.list_container);
    /**
     * 设置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);////设置好
    ```
  1. Activity和Fragment跳转动画
* Activity跳转动画
  在**startActivity()方法之后** 或者 **finish()/super.onBackPressed()方法之后**执行:
  `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:**如下示例代码,使用系统默认的Fragment加载动画。
  * **FragmentTransaction.setCustomAnimations:**如下示例代码,使用自定义的View动画加载。
  * 注意,这种方式对于DialogFragment是无效的,因为DialogFragment没有对应的父容器,本身就是固定的。
  * 示例代码:
  定义`fragment_enter_anim.xml`文件(`fragment_exit_anim.xml`文件类似):
  ```


android:fillAfter="true"
android:interpolator="@android:anim/linear_interpolator"
android:zAdjustment="normal"
android:shareInterpolator="true"
android:repeatMode="restart"
android:duration="1000"
>
android:fromYDelta="-600"
android:toYDelta="0"
android:startOffset="500"
/>
android:fromDegrees="-90"
android:toDegrees="0"
android:pivotY="50%"
android:pivotX="50%"
/>
android:fromAlpha="0.5"
android:toAlpha="1"
/>

然后,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:

Android动画常用格式和注意事项总结_第4张图片

二、 帧动画

  • gif:


    Android动画常用格式和注意事项总结_第5张图片
  • 首先,在/res/drawable目录下创建xml文件:


android:oneshot="true">



* 然后让指定view去设置`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代码方式动态加载动画。

这里,使用的是/res/animator/xxx.xml文件,而不是/res/anim/xxx.xml,java中的写法是:`R.animator.xxx`而不是`R.anim.xxx`
首先,定义xml属性:


android:ordering="together"
>

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"/>
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"/>

然后,在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);///设置图片内容
AnimatorSet set = new AnimatorSet();///创建属性动画集
set.playTogether(///“playTogether”表示所有的子动画同时运作
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. **关于Android Animation的setFillBefore、setFillAfter和setFillEnable:**
  * 如果是独立的Animation,只有setFillAfter有效,设置为true动画结束后保持最后的状态。
  * 如果是AnimationSet中的Animation,因为Animation的作用周期可能位于整个AnimationSet动画周期的中间一部分,setFillBefore设置的是在这个动画被执行前是否启用这个动画的第一帧效果填充开始前的动画,setFillAfter设置的是在这个动画结束后是否保留这个动画的最后一帧的效果填充后面的动画,而这两个设置必须同时设置setFillEnable。
  * 如果想这个AnimationSet结束后保留最后的结果,需要设置AnimationSet的setFillAfter。
  > 补充:当setFillEnable为false时,通过查看源码可知在AnimationSet中自身的动画周期不受setFillBefore和setFillAfter控制;当Animation独立存在时,或AnimationSet的setFillAfter为true时,ViewGroup会读取getFillAfter值,如果为true,不clearAnimation,也就保持了最终的状态。
2. **让view自转:**
  * 方式一:java代码动态设定旋转动画的轴心为中心:
    `animation = new RotateAnimation(0, +720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);`
  * 方式二:xml中设置``属性中`pivotY="50%"`和`pivotX="50%"`来设定轴心为中点:
    ```

    ```
3. **OOM问题:**
  主要出现在帧动画当中,当图片数量较多并且图片较大时就容易出现OOM(App拥有的内存已经装不下图片了)。所以,尽量避免使用帧动画。
4. **内存泄露:**
  属性动画中有一类无线循环动画,这类动画需要在Activity退出时及时停止,否则将导致Activity无法释放从而造成内存泄露(ML)。通过验证发现View动画不存在此问题。
5. **兼容性问题:**
  动画在Android3.0(API 11)以下的系统上有兼容性问题,所以,需要适当使用兼容库如nineoldandroids等进行适配(目前大多Android手机版本在4.0以上,这一点问题不大)
6. **View动画的问题:**
  View动画是对View的影像做动画,不是真的改变View的布局属性和状态,所以,有时会出现:动画完成后View无法隐藏的情况,即`setVisibility(View.GONE);`失效。解决方法:调用`view.clearAnimation()`清除View动画即可。
7. **不要使用px:**
  由于不同尺寸的手机,dpi不一样,所以,用dp设定动画更容易适配不同手机。下面分享个人的一个dp转px的工具类DiaplayUtil:
  ```
/**
* 屏幕显示相关工具类
* (px ,sp, dp 等转换)
*/
public class DisplayUtil {
  private static Map screenMap = new HashMap<>();
  /**
   * 将px值转换为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值转换为px值,保证尺寸大小不变
   * @return
   */
  public static int dip2px(float dipValue, Activity activity) {
      return (int) (dipValue * getScreenMsg(activity).get(ScreenEnum.Density) + 0.5f);
  }
  /**
   * 将px值转换为sp值,保证文字大小不变
   *
   * @return
   */
  public static int px2sp(float pxValue, Activity activity) {
      return (int) (pxValue / getScreenMsg(activity).get(ScreenEnum.ScaledDensity) + 0.5f);
  }
  /**
   * 将sp值转换为px值,保证文字大小不变
   * @return
   */
  public static int sp2px(float spValue, Activity activity) {
      return (int) (spValue * getScreenMsg(activity).get(ScreenEnum.ScaledDensity) + 0.5f);
  }
  /**
   * 获取屏幕尺寸等信息
   */
  public static Map 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的影像,不是实际的view布局位置的移动,所以,当view拥有点击事件时,位移动画完成后:view的点击位置和view的实际位置会不一致(3.0以前:View动画和属性动画都一样,新位置无法点击,旧位置可以点击;3.0及以后:属性动画的单击事件触发位置为移动后的位置,但是View动画仍在原位置。)
  解决方法:改变view的布局属性,让view在动画结束时实际移动。
  ```
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在动画结束时实际移动
        getImageView().offsetTopAndBottom(-160);
}
  ```
9. **硬件加速:**
  建议在频繁使用到动画的地方使用硬件加速。
  Android从3.0(API11)开始,在绘制View的时候支持硬件加速,充分利用GPU的特性,使得绘制更加平滑,但是会多消耗一些内存。
  * Application级别:``
  * Activity级别:``
  * Window级别:

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

     注意: 目前为止,Android还不支持在Window级别关闭硬件加速.
   * View级别:`myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);`
     注意:目前为止,Android还不支持在View级别开启硬件加速。
   * 检测是否启用了硬件加速:

// 方法一
// 此方法返回true,如果myView挂在一个开启了硬件加速的Window之下,
// 也就是说,它在绘制的时候不一定使用了硬件加速,getDrawingCache
myView.isHardwareAccelerated();
// 方法二
// 返回true,如果canvas在绘制的时候启用了硬件加速
// 尽量采用此方法来判断是否开启了硬件加速
canvas.isHardwareAccelerated();


   * 硬件加速可能出现的限制:
      1.某些UI元素没有显示:可能是没有调用invalidate
      2.某些UI元素没有更新:可能是没有调用invalidate
      3.绘制不正确:可能使用了不支持硬件加速的操作, 需要关闭硬件加速或者绕过该操作
      4.抛出异常:可能使用了不支持硬件加速的操作, 需要关闭硬件加速或者绕过该操作
---
## Demo与推荐文章
  本文中用到的一些相关的R.anim.xxx文件可能没有详细说明代码,可下载Demo
Demo地址:https://github.com/androidjp/anim-demo
Android动画推荐文章(想了解更详细可以看看):
http://www.cnblogs.com/ldq2016/p/5407061.html

你可能感兴趣的:(Android动画常用格式和注意事项总结)