7 Android动画深入分析
Android动画分为三种:
- View动画
- 帧动画
- 属性动画
本章学习内容:
- 介绍View动画和自定义View动画
- View动画一些特殊的使用场景
- 对属性动画全面性的介绍
- 使用动画的一些注意事项
7.1 View动画
View动画的作用对象是View,支持四种动画效果:
- 平移
- 缩放
- 旋转
- 透明
7.1.1 View动画的种类
上述四种变换效果对应着Animation四个子类:TranslateAnimation
、ScaleAnimation
、RotateAnimation
和AlphaAnimation
。这四种动画皆可以通过XML定义,也可以通过代码来动态创建。
xml定义动画
<set>
标签表示动画集合,对应AnimationSet类,可以包含一个或若干个动画,内部还可以嵌套其他动画集合。两个属性:android:interpolator
表示动画集合所采用的插值器,插值器影响动画速度,比如非匀速动画就需要通过插值器来控制动画的播放过程。android:shareInterpolator
表示集合中的动画是否和集合共享同一个插值器,如果集合不指定插值器,那么子动画就需要单独指定所需的插值器或默认值。
<translate>
、<scale>
、<rotate>
、<alpha>
这几个子标签分别代表四种变换效果。定义完View动画的xml后,通过以下代码应用动画:
Aniamation anim = AnimationUtils.loadAnimation(context,R.anim.animation_test); view.startAnimation(anim);
代码动态创建动画
AlphaAnimation alphaAnimation = new AlphaAnimation(0,1); alphaAnimation.setDuration(1500); view.startAnimation(alphaAnimation);
7.1.2 自定义View动画
需要继承Animation
这个抽象类,重写它的initialize
和applyTransformation
方法。在initialize
方法中做一些初始化工作,在applyTransformation
中进行相应的矩阵变换即可,很多时候需要采用Camera
来简化矩阵变换的过程。自定义View动画的过程主要是矩阵变换的过程。
7.1.3 帧动画
帧动画是顺序播放一组预先定义好的图片,使用简单,但容易引起OOM,所以在使用帧动画时应尽量避免使用过多尺寸较大的图片。
7.2 View动画的特殊使用场景
7.2.1 LayoutAnimation
作用于ViewGroup,为ViewGroup指定一个动画,当它的子元素出场时都会具有这种动画效果,一般用在ListView上。
7.2.2 Activity的切换效果
我们可以自定义Activity的切换效果,主要通过在startActivity
或者finish
的后面增加overridePendingTransition(int enterAnim , int exitAnim)
方法
7.3 属性动画
API 11后加入,可以在一个时间间隔内完成对象从一个属性值到另一个属性值的改变。因此与View动画相比,属性动画几乎无所不能,只要对象有这个属性,它都能实现动画效果。API11以下可以通过nineoldandroids
库来兼容以前版本。
属性动画有以下三种使用方法:
ObjectAnimator
ObjectAnimator.ofFloat(view,"translationY",values).start();
ValueAnimator
ValueAnimator colorAnim = ObjectAnimator.ofInt(view,"backgroundColor",/*red*/0xffff8080,/*blue*/0xff8080ff); colorAnim.setDuration(2000); colorAnim.setEvaluator(new ArgbEvaluator()); colorAnim.setRepeatCount(ValueAnimator.INFINITE); colorAnim.setRepeatMode(ValueAnimator.REVERSE); colorAnim.start();
AnimatorSet
AnimatorSet set = new AnimatorSet(); set.playTogether(animator1,animator2,animator3); set.setDuration(3*1000).start();
也可以通过在xml中定义在
res/animator/
目录下。具体如下:<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator ....../> <animator ....../> </set>
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(context , R.animator.anim);
set.setTarget(view);
set.start();
<set>
标签对应AnimatorSet
,<animator>
对应ValueAnimator
,而<objectAnimator>
则对应ObjectAnimator
。
7.3.2 理解差值器和估值器
- 时间插值器(TimeInterpolator)的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator(线性插值器:匀速动画),AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快),DecelerateInterpolator(减速插值器:动画越来越慢)。
- 估值器(TypeEvaluator)的作用是根据当前属性改变的百分比来计算改变后的属性值。系统预置有IntEvaluator 、FloatEvaluator 、ArgbEvaluator。
- 具体来说 对于一个作用在view上改变其宽度属性、持续40ms的属性动画来说,就是 当时间t=20ms时,时间流逝了50%,那么view的宽度属性应该改变了多少呢?这个就由Interpolator和Evaluator的算法来决定。
7.3.3 属性动画的监听器
public static interface AnimatorListener {
void onAnimationStart(Animator animation); //动画开始
void onAnimationEnd(Animator animation); //动画结束
void onAnimationCancel(Animator animation); //动画取消
void onAnimationRepeat(Animator animation); //动画重复播放
}
为了方便开发,系统提供了AnimatorListenerAdapter类,它是AnimatorListener的适配器类,可以有选择的实现以上4个方法。
/**
* Implementors of this interface can add themselves as update listeners
* to an <code>ValueAnimator</code> instance to receive callbacks on every animation
* frame, after the current frame's values have been calculated for that
* <code>ValueAnimator</code>.
*/
public static interface AnimatorUpdateListener {
/**
* <p>Notifies the occurrence of another frame of the animation.</p>
*
* @param animation The animation which was repeated.
*/
void onAnimationUpdate(ValueAnimator animation);
}
AnimatorUpdateListener会监听整个动画的过程,动画由许多帧组成的,每播放一帧,onAnimationUpdate就会调用一次。
7.3.4 对任意属性做动画
- 属性动画要求作用的对象提供该属性的get和set方法,属性动画根据外界传递的该属性的初始值和最终值,通过多次调用set方法来实现动画效果。
如果被作用的对象没有set/get方法,可以:
- 请给你的对象加上get和set方法,如果你有权限的话(对于SDK或者其他第三方类库的类无法加上的)
用一个类来包装原始对象,间接为其提供get和set方法
//包装View类 用于给属性动画调用 从而包装了set get public class ViewWrapper { private View target; public ViewWrapper(View target) { this.target = target; } public int getWidth() { return target.getLayoutParams().width; } public void setWidth(int width) { target.getLayoutParams().width = width; target.requestLayout(); } } //使用: ViewWrapper wrapper = new ViewWrapper(mButton); ObjectAnimator.ofInt(mButton,"width",500).setDuration(3000).start();
采用ValueAnimator,监听动画过程,自己实现属性的改变;
private void performAnimate(final View target, final int start, final int end) { ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100); valueAnimator.addUpdateListener(new AnimatorUpdateListener() { // 持有一个IntEvaluator对象,方便下面估值的时候使用 private IntEvaluator mEvaluator = new IntEvaluator(); @Override public void onAnimationUpdate(ValueAnimator animator) { // 获得当前动画的进度值,整型,1-100之间 int currentValue = (Integer) animator.getAnimatedValue(); Log.d(TAG, "current value: " + currentValue); // 获得当前进度占整个动画过程的比例,浮点型,0-1之间 float fraction = animator.getAnimatedFraction(); // 直接调用整型估值器通过比例计算出宽度,然后再设给Button target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end); target.requestLayout(); } }); valueAnimator.setDuration(5000).start(); }
7.3.5 属性动画的工作原理
属性动画需要运行在有Looper的线程中,系统通过反射调用被作用对象get/set方法。
7.4 使用动画的注意事项
- 使用帧动画时,当图片数量较多且图片分辨率较大的时候容易出现OOM,需注意,尽量避免使用帧动画。
- 使用无限循环的属性动画时,在Activity退出时即使停止,否则将导致Activity无法释放从而造成内存泄露。
- 动画在3.0以下的系统存在兼容性问题,特殊场景可能无法正常工作,需做好适配工作。
- View动画是对View的影像做动画,并不是真正的改变了View的状态,因此有时候会出现动画完成后View无法隐藏(setVisibility(View.GONE)失效),这时候调用
view.clearAnimation()
清理View动画即可解决。 - 不要使用px,使用px会导致不同设备上有不同的效果。
- View动画是对View的影像做动画,View的真实位置没有变动,动画完成后的新位置是无法触发点击事件的。属性动画是真实改变了View的属性,所以动画完成后的位置可以接受触摸事件。
- 使用动画的过程中,使用硬件加速可以提高动画的流畅度。