1.引言
早期Android提供了两种动画方式,逐帧动画(frame-by-frame animation)和补间动画(tweened animation)。逐帧动画的工作原理如同动画片一样,通过一张张单独的图片连贯起来播放。补间动画则是可以对View进行一系列的动画操作,包括淡入淡出、缩放、平移、旋转四种。
这里就会有人有疑问了,然而我们还是要看到这两种动画的一些限制,犹其是补间动画这种非常常用的动画,在功能和可扩展方面还是有相当大的局限性,主要体现在以下三个方面:
补间动画是只能够作用在View上的,无法非View的对象进行动画操作。这是什么意思呢?举个例子,比如说我们有一个自定义的View,在这个View当中有一个Point对象用于管理坐标,然后在onDraw()方法当中就是根据这个Point对象的坐标值来进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义View的动画效果就有了。显然,补间动画是不具备这个功能的,这是它的第一个缺陷。
补间动画机制就是使用硬编码的方式来完成的,功能上无法扩展。比如想让view的背景色变换,它是没办法做到的
补间动画还有一个致命的缺陷,就是它只是改变了View的显示效果而已,而不会真正去改变View的属性。什么意思呢?比如说,现在屏幕的左上角有一个按钮,然后我们通过补间动画将它移动到了屏幕的右下角,现在你可以去尝试点击一下这个按钮,点击事件是绝对不会触发的,因为实际上这个按钮还是停留在屏幕的左上角,只不过补间动画将这个按钮绘制到了屏幕的右下角而已。
为了解决上述的问题,Android团队推出了新的动画机制:Property Animation,也就是属性动画。
二、ValueAnimator
在我们的动画机制中,有两个类是比较基本的,一个是ValueAnimator,一个是ObjectAnimator,前面一个是基类,后一个是子类,但使用更为方便,也很常用。
ValueAnimator是整个属性动画机制当中最核心的一个类,前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。
注意,ValueAnimator操作的只是值的变化,我们可以为看下面这个例子:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
Log.d("TAG", "cuurent value is " + currentValue);
}
});
anim.start();
这个里面我们进行的操作主要是将数值0到1进行变化,变化的时间为300毫秒,而且设置了一个updateListener,来监听并打印数值的变化。可以料到,程序运行的结果就是在300毫秒内,打印了一串0-1之间的数。
但是说的是属性动作,只模拟了数值的变化怎么行。是的,ValueAnimator只是将数值进行操作,本身并没有涉及到属性。在使用中需要配合属性来进行。
它的好处在于:不必针对某个单一的属性,你可以自己根据当前动画的计算值,来操作任何属性,只要这个属性有getter,setter,比如【希望一个动画能够让View既可以放大、背景又能够由浅变深(3个属性scaleX,scaleY,alpha)】,就可以利用上述构造出来的anim,直接设置:
//利用单个ValueAnimator,对多个属性进行操作
View.setScalX(anim);
View.setScalY(anim);
View.setAlpha(anim);
这里就不得不扯一下ObjectAnimator的一个非典型用法:
public void rotateyAnimRun(final View view)
{
ObjectAnimator anim = ObjectAnimator//
.ofFloat(view, "suibian", 0.0F, 1.0F)//
.setDuration(500);//
anim.start();
anim.addUpdateListener(new AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
float cVal = (Float) animation.getAnimatedValue();
view.setAlpha(cVal);
view.setScaleX(cVal);
view.setScaleY(cVal);
}
});
}
这一串代码用了ObjectAnimator,实现的功能和前面用ValueAnimator一模一样,为啥呢?主要看这一句。
ObjectAnimator anim = ObjectAnimator//
.ofFloat(view, “suibian”, 0.0F, 1.0F)//
.setDuration(500);
解释一样,view就是我们要操作的对象,第二个参数是属性,看到我们传了一个”suibian ”进去,“suibian”并不是这个对象有的属性,于是ObjectAnimator就退化为ValueAnimator对第三个第四个参数进行值操作,使用是就需要利用属性的setter方法了。
既然说到了操作多个动画,这里再提另外一种方法:使用propertyValuesHolder,听名字就可以猜到用法
public void propertyValuesHolder(View view)
{
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f,
0f, 1f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f,
0, 1f);
PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f,
0, 1f);
ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY,pvhZ).setDuration(1000).start();
}
三、ObjectAnimator
谈起ObjectAnimator,还是要对比ValueAnimator.ObjectAnimator就直观的特点就是可以直接对任意对象的任意属性进行动画操作的,比如说View的alpha属性。
但说到继承关系,ObjectAnimator归根结底调用的都是ValueAnimator,所以呢,其实ObjectAnimator能够实现的用ValueAnimator照样能够实现。
由于参数比较直观,我直接用一个例子来展示了:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "scaleX", 1.0f, 0.5f,1.0f);
animator.setDuration(5000);
animator.start();
可以看出,我们对一个textView在X轴方向上进行缩放操作,与前面的不同,这里我们传了了三个参数,1.0f—>0.5f—>1.0f,对应的效果就是textView在x方向上先缩小到一半,再放大到原来大小。
四、组合动画
到这里,有读者可能想问,如果有多个动画,而且动画的播放有一个先后顺序怎么办。嗯,Android团队肯定也想到这个问题了。
实现组合动画功能主要需要借助AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:
after(Animator anim) 将现有动画插入到传入的动画之后执行
after(long delay) 将现有动画延迟指定毫秒后执行
before(Animator anim) 将现有动画插入到传入的动画之前执行
with(Animator anim) 将现有动画和传入的动画同时执行
直接上代码:
public void playWithAfter(View view)
{
float cx = mBlueBall.getX();
ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX",
1.0f, 2f);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY",
1.0f, 2f);
ObjectAnimator anim3 = ObjectAnimator.ofFloat(mBlueBall,
"x", cx , 0f);
ObjectAnimator anim4 = ObjectAnimator.ofFloat(mBlueBall,
"x", cx);
/**
* anim1,anim2,anim3同时执行
* anim4接着执行
*/
AnimatorSet animSet = new AnimatorSet();
animSet.play(anim1).with(anim2);
animSet.play(anim2).with(anim3);
animSet.play(anim4).after(anim3);
animSet.setDuration(1000);
animSet.start();
}
}
那一串的playwith,playafter就是执行的先后。虽然是支持链式编程的,但是还是要谨慎的,不要挤在一起写,一句就写完,分开来写比较好。
最后提一下监听器,addListener里面总共实现了四个接口,onAnimationStart()方法会在动画开始的时候调用,onAnimationRepeat()方法会在动画重复执行的时候调用,onAnimationEnd()方法会在动画结束的时候调用,onAnimationCancel()方法会在动画被取消的时候调用。
有人会觉得挺麻烦的,没关系,Android提供了一个适配器类,叫作AnimatorListenerAdapter,使用这个类就可以解决掉实现接口繁琐的问题了,可以实现自己需要的接口:
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
在xml中实现动画的方式也是极其简单的,我们可以在res目录下面新建一个animator文件夹,所有属性动画的XML文件都存放在这个文件夹当中。
XML文件中主要有三种标签:
<animator> 对应代码中的ValueAnimator
<objectAnimator> 对应代码中的ObjectAnimator
<set> 对应代码中的AnimatorSet
而如果我们想将一个视图的alpha属性从1变成0,就可以这样写:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="alpha"/>
然后怎么在代码中调起这段动画呢,一句话搞定
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);
animator.setTarget(view);
animator.start();
loadAnimator进来,和使用layout很类似有木有,就是这么简单。
有兴趣的可以去看看鸿洋用动画实现的线:
http://blog.youkuaiyun.com/lmj623565791/article/details/38067475