先来讲讲android动画分类:
android3.0之前,1)补间动画Tween Animation
Animation.java
res/anim/a.xml
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@anim/my_overshoot_interpolator"
android:fromXScale="1.0"
android:toXScale="3.0"
android:fromYScale="1.0"
android:toYScale="3.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="700" />
ImageView image =(ImageView) findViewById(R.id.image);
Animation anim= AnimationUtils.loadAnimation(this,R.anim.a);
image.startAnimation(hyperspaceJump);
2)帧动画Frame Animation(也叫Drawable Animation)
AnimationDrawable.java
res/drawable/rocket_thrust.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
rocketAnimation.start();
使用Frame Animation注意一下问题:
要在代码中调用Imageview的setBackgroundResource方法,如果直接在XML布局文件中设置其src属性当触发动画时会FC。
在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次。
最后一点是SDK中提到的,不要在onCreate中调用start,因为AnimationDrawable还没有完全跟Window相关联,如果想要界面显示时就开始动画的话,可以在onWindowFoucsChanged()中调用start()。
Tween Animation、Frame Animation只能用于View,被归类为View Animation.
android3.0之后,增加
3)属性动画Property Animation
ValueAnimator, ObjectAnimator, or AnimatorSet.
沿着一定的时间顺序,通过改变View的属性,从而得到的动画的效果。
属性动画几乎可以定义出你想要的所有动效。
ValueAnimator是整个属性动画机制当中最核心的一个类,前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。
ObjectAnimator ValueAnimator的子类,提供了对目标对象的属性动画的支持。
- AnimatorSet 可以同时运行一组动画
- PropertyValuesHolder 他代表一个在动画运行中需要过度到的值。
- TypeEvaluator 实现此接口的实例,将决定AnimatorUpdateListener接收到的值。
private void performAnimate() {
ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start();
}
@Override
public void onClick(View v) {
if (v == mButton) {
performAnimate();
}
}
上述代码运行一下发现没效果,其实没效果是对的,如果你随便传递一个属性过去,轻则没动画效果,重则程序直接Crash。
下面分析下属性动画的原理:
属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。总结一下,你对object的属性xxx做动画,如果想让动画生效,要同时满足两个条件:
1. object必须要提供setXxx方法,如果动画的时候没有传递初始值,那么还要提供getXxx方法,因为系统要去拿xxx属性的初始值(如果这条不满足,程序直接Crash)
2. object的setXxx对属性xxx所做的改变必须能够通过某种方法反映出来,比如会带来ui的改变啥的(如果这条不满足,动画无效果但不会Crash)
以上条件缺一不可
那么为什么我们对Button的width属性做动画没有效果?这是因为Button内部虽然提供了getWidth和setWidth方法,但是这个setWidth方法并不是改变视图的大小,它是TextView新添加的方法,View是没有这个setWidth方法的,由于Button继承了TextView,所以Button也就有了setWidth方法。下面看一下这个getWidth和setWidth方法的源码:
用一个类来包装原始对象,间接为其提供get和set方法
这是一个很有用的解决方法,是我最喜欢用的,因为用起来很方便,也很好理解,下面将通过一个具体的例子来介绍它
private void performAnimate() {
ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
}
@Override
public void onClick(View v) {
if (v == mButton) {
performAnimate();
}
}
private static class ViewWrapper {
private View mTarget;
public ViewWrapper(View target) {
mTarget = target;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
上述代码5s内让Button的宽度增加到500px,为了达到这个效果,我们提供了ViewWrapper类专门用于包装View,具体到本例是包装Button,然后我们对ViewWrapper的width熟悉做动画,并且在setWidth方法中修改其内部的target的宽度,而target实际上就是我们包装的Button,这样一个间接属性动画就搞定了。
首先说说啥是ValueAnimator,ValueAnimator本身不作用于任何对象,也就是说直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。还是不太明白?没关系,下面用例子说明
[java] view plain copy 在CODE上查看代码片派生到我的代码片
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 = currentValue / 100f;
//这里我偷懒了,不过有现成的干吗不用呢
//直接调用整型估值器通过比例计算出宽度,然后再设给Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
@Override
public void onClick(View v) {
if (v == mButton) {
performAnimate(mButton, mButton.getWidth(), 500);
}
}
上述代码的动画效果图和采用ViewWrapper是一样的,请参看上图。关于这个ValueAnimator我要再说一下,拿上例来说,它会在5000ms内将一个数从1变到100,然后动画的每一帧会回调onAnimationUpdate方法,在这个方法里,我们可以获取当前的值(1-100),根据当前值所占的比例(当前值/100),我们可以计算出Button现在的宽度应该是多少,比如时间过了一半,当前值是50,比例为0.5,假设Button的起始宽度是100px,最终宽度是500px,那么Button增加的宽度也应该占总增加宽度的一半,总增加宽度是500-100=400,所以这个时候Button应该增加宽度400*0.5=200,那么当前Button的宽度应该为初始宽度+ 增加宽度(100+200=300)。上述计算过程很简单,其实它就是整型估值器IntEvaluator的内部实现,所有我们不用自己写了,直接用吧。
写在后面的话
1.View动画(渐变动画)的功能是有限的,大家可以尝试使用属性动画
2.为了在各种安卓版本上使用属性动画,你需要采用nineoldandroids,它是GitHub开源项目,jar包和源码都可以在网上下到
3.再复杂的动画都是简单动画的合理组合,再加上本文介绍的方法,可以对任何属性作用动画效果,也就是说你几乎可以做出任何动画
4.属性动画中的插值器(Interpolator)和估值器(TypeEvaluator)很重要,它是实现非匀速动画的重要手段,你应该试着搞懂它,最好你还能够自定义它们