Android动画之属性动画
在日常开发中,我们可能会需要实现各种炫酷的效果,比如旋转、放大、缩小等,在Android 3.0以前,我们可以通过逐帧动画和补间动画来实现,而在Android 3.0之后,Andorid 新增了属性动画,那么属性动画和其他两种动画又有什么区别呢?
一、三种动画对比
通常我们将许多图片连贯起来播放,这样看起来就是动画的效果,这样的动画就叫做逐帧动画。
缺点:动画的效果取决于我们放入的图片,对于开发而言,可操作性较小,更多的则是依于放入的图片(如果动画复杂,可能放入十几二十个图)。
补间动画通常是我们定义好了动画开始和结束的状态,然后由系统来完成中间的计算工作,然后呈现出动画的效果。
优点:和逐帧动画相比,我们可以实现的动画效果更加复杂,同时也不依赖与图片的导入。
缺点:这里的动画只是在绘制上发生了改变,也就是视觉上的改变,但是view的属性并没有改变。比如说讲一个按钮位移后,点击位移后的位置无法触发点击事件,而点击按钮动画前的位置则可以触发点击事件,说明view的属性并没有改变。
其次,补间动画的效果只能局限于位移、旋转、放缩、透明度四种动画,其他的动画需要我们自己去实现。
属性动画则是在补间动画的基础上,同样我们也会定义开始和结束状态,然后由系统 去计算。
优点:不仅仅改变了界面显示也改变了view或者其他对象的属性;
不仅仅作用与view,还可以作用与其他对象;
通过上面的对比,我们对属性动画也有了一个基本的认识,一定要记住,属性动画改变的是对象的属性,这一点属性动画这个名称上也能看出来。
二、属性动画之初识
在了解属性动画具体的使用前,首先我们来看两个类ValueAnimator和ObjectAnimator,通过这两个类我们从原理上来分析属性动画。
1、Animator
Animator是属性动画的抽象基类,我们常用到的AnimatorSet、ValueAnimator、ObjectAnimator都是它的子类,在这个抽象类中定义了一些我们常用的方法,这里我们列举一些常见的方法:
方法名
|
说明
|
addListener
( Animator.AnimatorListener
listener)
|
添加动画监听
|
setDuration
(long duration)
|
设置动画执行时间,单位ms
|
setInterpolator
( TimeInterpolator
value)
|
设置插值器
|
|
开始动画
|
end
()
|
结束动画
|
2、ValueAnimator
继承关系:
ValueAnimator->Animator->Object
也就是说ValueAnimator是Animator的子类。而前面我们介绍的Animator中主要包含了关于动画的开始i、结束、设置执行时间、添加监听器、设置插值器等,而我们设置开始和结束值然后计算并 实现动画则是在ValueAnimator中来完成的。
例如:我们实现一个从0到1的动画变化,很简单,如下:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.start();
这里我们就实现了一个从0 到 1 的变化,但是很可惜我们运行后看不到任何变化,为什么呢?
原因是我们这里确实是一个动画,也确实是一个从0到1的动画,但是它只是对值做了动画,并没有对对象做动画,也没有和任何对象绑定,更没有与对象的某个属性相绑定,因此我们看不到任何效果,不那么我们如何实现对象的属性动画呢?
这就需要提到我们今天的重点:ObjectAnimator
3、ObjectAnimator
ObjectAnimator是我们常用的属性动画类,它继承了ValueAnimator,实现了和对象属性的绑定。
(1)ObjectAnimator与对象属性的关系
前面我们提到,属性动画实际上是改变了对象的属性,那么ObjectAnimtor又是如何改变对象的属性呢?
这里我们以改变TextView的透明度为例子:
ObjectAnimator animator = ObjectAnimtor.ofFloat(textView,"aplpha",1f,0f,1f);
animator.setDuration(1000);
animator.start();
这里我们需要注意
ObjectAnimtor.ofFloat(textView,"aplpha",1f,0f,1f);我们在ObjectAnimator的ofFloat()方法的第二个参数中输入"aplpha",而在之后的参数中则输入透明度的变化,这里是从1->0->1,经过三个变化。那么是否可以认为是因为我们改变了TextView中有alpha这个属性,所以改变了其透明度?
答案是错误的,因为TextView中并没有alpha属性,那么我们这里是如何改变TextView的属性呢?
事实上,ObjectAnimator是通过执行动画的对象的setX()和getX()来实现对该对象的属性的改变(这不正是我们当时刚学JAVA时提到的通过get和set方法来改变对象属性j),而这里的X则是我们在ofFloat中输入的alpha,也就是说我们上面的例子中是通过textView的setAlpha()来改变其透明度。
那么它又是如何通过这个setAlpha()实现从透明到不透明再到透明的动态效果呢?
这个就需要谈到属性动画的插值器,这个我们下一节会重点讲插值器。
(2)常见的ObjectAnimator动画
我们通常通过使用public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
<1>第一个参数 Object target Object对象,可以看到这里只要是Object对象就可以,不局限于view
<2>第二个参数String propertyName 属性名称 ,比如透明度"alpha"’、旋转"rotation"
<3>第三个参数float... values
不定长变量,表示对应属性的变化值,比如说位移动画,传入10,表示向右平移10
这里我们需要区分一个概念,就是第三个参数float... values 是相对值还是绝对值,怎么理解这句话呢?
就拿x轴位移来说,我们传入一个x1,x2,那么这里x1,x2是表示屏幕上的坐标(x1,y1),(x2,y2)呢,还是说移动的距离是x1,x2呢,这是完全不同的两个概念的。其实对于属性动画而言,我们和实际的坐标点并不挂钩,也就是我们是相对于对象自身来实现对象,也就是说我们的值是相对值,相对于我们view,比如说这里的x1,其实表示的是view向右平移10,而不是说移动到坐标点(10,y),那么我们传入的x1具体对应到屏幕上是哪个点则不需要我们去管,view自己会根据我们传入的变化值去计算。
ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);
ObjectAnimator.ofFloat(textView,"alpha",0f);
ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f,1f,0f,1f);//这里表示textview在执行动画前是不透明,然后第一个状态1f,仍然是不透明,然后第二个状态0f,则由不透明变为透明,之后第三个状态1f,则由透明变为不透明
ObjectAnimator.ofFloat(textview,"translationX",10);//这里没有设置初始状态,因为ofFloat方法中只需要设置结束状态就可以,这里的含义是view沿x轴方向向右平移10
ObjectAnimator.ofFloat(textview,"translationX",-10);//这里的含义是view沿x轴方向向左平移10
ObjectAnimator.ofFloat(textview,"translationX",0,10);//这里的含义是view沿x轴方向向右平移10,相当于第一个的情形
ObjectAnimator.ofFloat(textview,"translationX",10,20);//这里的含义是view沿x轴方向向右平移10距离,继续享向右平移20的距离
ObjectAnimator.ofFloat(textview,"translationX",0,-10,20,30);//这里的含义是view沿x轴方向向左平移10距离,然后向右平移20的距离,再向右平移30的距离
ObjectAnimator.ofFloat(textview, "scaleY", 1f, 3f, 1f);
ObjectAnimator.ofFloat(textview, "rotation" 10f, 360f);
4、组合动画
在很多时候我们会将旋转、位移、放缩等结合在一起使用,此时就需要用到组合动画,顾名思义就是将动画效果组合在一起。
实现组合动画,我们就需要使用AnimatorSet这个类,AnimatorSet这个类也是继承了Animator抽象类。而要实现组合动画,那么就需要以下四个方法:
-
after(long delay
),表示将现有动画延时delay毫秒
-
before(Animator anim),表示将anim动画插入到现有动画之前执行
-
with(Animator anim),表示将anim动画和现有动画同时执行
范例:
ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
可以看到fadeInOut和rotate同时执行,之后执行moveIn。
以上四个方法中均需要传入Animtor对象,比如说ValueAnimator或者是ObjectAniamtor对象。
三、Animator监听器
前面我们已经实现了基本的属性动画,那么接下来考虑这样一个场景,设计一个滑块,可以在屏幕内随机取点滑动,每滑到一个点,停留一秒,然后再滑动到下一个点。
这样我们就需要考虑以下两个问题:
首先我们解决第一个问题,滑动问题我们可以通过平移动画来解决。如下:
假设初始状态下滑块的点的位置为(x0,y0),我们需要移动到第二个点(x1,y1)。
animatorX = ObjectAnimator.ofFloat(mIndicator, "translationX", x1-x0);
animatorY = ObjectAnimator.ofFloat(mIndicator, "translationY", y1-y0);
这样我们的平移就解决了。那么如何在滑动完成后延时1S,然后移动到下一个点?简化这个问题,我们如何知道一次动画执行结束了?
这里我们就需要用到动画的监听器AnimatorListener
public static interface AnimatorListener {
/**
* Notifies the start of the animation.
*
* @param animation The started animation.
*/
void onAnimationStart(Animator animation);
/**
* Notifies the end of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.
*
* @param animation The animation which reached its end.
*/
void onAnimationEnd(Animator animation);
/**
* Notifies the cancellation of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.
*
* @param animation The animation which was canceled.
*/
void onAnimationCancel(Animator animation);
/**
* Notifies the repetition of the animation.
*
* @param animation The animation which was repeated.
*/
void onAnimationRepeat(Animator animation);
}
这里主要是四个方法:
结合到我们上面提到的例子,如下:
animator.addListener(new Animator.AnimatorListener()
{
@Override
public void onAnimationStart(Animator animation)
{
}
@Override
public void onAnimationEnd(Animator animation)
{
//执行延时和重新去掉平移操作
}
@Override
public void onAnimationCancel(Animator animation)
{
}
@Override
public void onAnimationRepeat(Animator animation)
{
}
});
确实,通过这样我们实现了在动画接收后执行延时和重新平移的操作,但是我们只需要onAnimationEnd(),而这里确调用了四个方法,显得很繁琐,因此我们可以考虑调用AnimatorListenerAdapter,如下:
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
这样我们只需要调
onAnimationEnd()这一个方法就可以了。
四、XML实现动画
在Andorid的目录结构下,我在res中创建一个animator的目录(注意区别anim目录,它是用来呈现补间动画和逐帧动画),然后再声明属性动画。
1、标签
对应ObjectAnimator
对应AnimatorSet
2、范例
(1)动画文件 btn_anim.xml
"
android:ordering="sequentially" >
android:duration="2000"
android:propertyName="translationX"
android:valueFrom="-500"
android:valueTo="0"
android:valueType="floatType" >
android:duration="3000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType" >
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" >
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" >
(2)代码
Animator anim = AnimatorInflater.laodAnimator(this,R.animator.btn_anim);
animator.setTargt(view);
animator.start();