作为交互的一部分,开发Android应用的时候时常会用到动画,这样可以使应用看起来不那么死板。某些较为特定的点击可以使用有趣的动画引起注意,进而可以获取更多的点击量。随着Android系统的不断完善,其动画机制也不断地改进,如早期的帧动画和补间动画,3.0之后加入属性动画,以及之后5.x加入的SVG矢量动画等。以下讲一些较为常用的动画实现方式,即帧动画、补间动画和属性动画。
逐帧动画(Frame Animation)
逐帧动画也称之为Drawable Animation,是通过一系列的图片按顺序播放使其连成动画,这里每一帧都有对应的图片。例如我需要将名字为“droidman01”到“droidman16”的16张图片连成帧动画动画,那么实现方式如下
XML中:
将每一帧的图片放入res的drawable中然后再res的anim中(没有则新建一个Directory命名为anim)或是Android Studio的drawable中新建一个动画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/droidman01"
android:duration="150"
/>
<item android:drawable="@drawable/droidman02"
android:duration="150"
/>
...
</animation-list>
imageView.setImageResource(R.drawable.frameanim);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
animationDrawable.start();
这里的oneshot表示是否只执行一次,为false则执行一次就结束动画,为true当然就是循环播放。android:duration则是该帧图片播放的持续时间。
java代码中:
AnimationDrawable animationDrawable = new AnimationDrawable();
for (int i = 1;i<16;i++){
int id;
if (i<10){
id = getResources().getIdentifier("droidman0"+i,"drawable",getPackageName());
}else{
id = getResources().getIdentifier("droidman"+i,"drawable",getPackageName());
}
Drawable drawable = getResources().getDrawable(id);
animationDrawable.addFrame(drawable,150);
}
imageView.setImageDrawable(animationDrawable);
animationDrawable.setOneShot(false);
animationDrawable.start();
在这里会看到一个方法getIdentifier(),这个方法是用来获取资源id的。第一个参数是资源名,第二个参数是资源类型可以为null,第三个参数是包名,也可以为空。这个方法对于获取资源名称类似的很有用,很方便。这里通过addFrame方法传入每帧的图片,并设置播放时间,同样代码中可通过setOneShot方法设置是否循环播放,最后通过start()方法开始播放动画。除此之外AnimationDrawable还提供其他方法如:
stop():停止播放动画
isRunning():放回boolean类型表示动画是否正在播放中
run():继续从下一帧开始播放
getNumberOfFrames():获取总帧数
getFrame(int index):获取指定帧图片
getDuration(int i):获取指定帧播放时间
isOneShot():是否是一次播放
上面动画的效果:
补间动画(Twwen Animation)
补间动画无需定义每一帧,只需定义开始和结束的关键两帧,中间的变化可设置插值器(Interpolator)来平滑过渡。补间动画提供了四个基本类型的动画:AlphaAnimation、RotateAnimation、TranslateAnimation和ScaleAnimation。其实这些动画使用起来相对不难,只要知道各个参数的含义即可。一下就一一说明。
AlphaAnimation
// 参数分别为开始和结束时的透明度,参数的取值区间[0,1]的float类型,即全透明到完全不透明。
AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
RotateAnimation
// 两参数分别为动画开始时的旋转角度和动画结束时的旋转角度,以点(0,0)为中心旋转
RotateAnimation rotateAnimation1 = new RotateAnimation(0,360);
// 旋转角度从0度到360度,旋转中心为点(100,100)
RotateAnimation rotateAnimation2 = new RotateAnimation(0,360,100,100);
// 旋转角度从0到360度。X轴方向上的旋转参考类型,相对参考类型的X坐标位置,Y轴方向上旋转的参考类型,相对参考类型的Y坐标位置,
// 此处参考类型有RotateAnimation.RELATIVE_TO_SELF自身,RotateAnimation.ABSOLUTE绝对位置,RotateAnimation.RELATIVE_TO_PARENT父控件,
// 如该写法为以自身的中心位置旋转
RotateAnimation rotateAnimation3 = new RotateAnimation(0,360,RotateAnimation.RELATIVE_TO_SELF,0.5F,RotateAnimation.RELATIVE_TO_SELF,0.5F);
TranslateAnimation
// 参数分别为动画开始X坐标,动画结束X坐标,动画开始Y坐标,动画结束X坐标。即动画效果是从(0,0)平移到(200,300)位置
TranslateAnimation translateAnimation1 = new TranslateAnimation(0,200,0,300);
// 和旋转的一样,有着参考类型,该效果为从该控件的(0,0)开始到控件宽高的2倍位置
TranslateAnimation translateAnimation2 = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_SELF,0F,TranslateAnimation.RELATIVE_TO_SELF,0F,
TranslateAnimation.RELATIVE_TO_SELF,2F,TranslateAnimation.RELATIVE_TO_SELF,2F);
ScaleAnimation
// 参数分别为动画开始时的X轴坐标伸缩尺寸,动画结束时X轴伸缩尺寸,动画开始时Y轴伸缩尺寸,动画结束时Y轴伸缩尺寸。其中尺寸数值为该空间原始大小的倍数。
// 该效果为以点(0,0)开始向外放大2倍
ScaleAnimation scaleAnimation1 = new ScaleAnimation(0f,2f,0f,2f);
// 前四个参数与上一样,后两个为缩放中心
ScaleAnimation scaleAnimation2 = new ScaleAnimation(0f,2f,0f,2f,100,200);
// 前四个参数与上一样,后四个为参考类型和参考相对位置,如此效果为以自身中心X,Y轴都伸展为原来的2倍
ScaleAnimation scaleAnimation3 = new ScaleAnimation(0f,2f,0f,2f,ScaleAnimation.RELATIVE_TO_SELF,0.5F,ScaleAnimation.RELATIVE_TO_SELF,0.5F);
使用时只需设置动画时间即可开启动画,以透明动画为例,其他用法类似
//动画持续时间
alphaAnimation.setDuration(1000);
//动画结束时保留状态
alphaAnimation.setFillAfter(true);
//设置插值器
alphaAnimation.setInterpolator(new LinearInterpolator();
//开启动画
imageView.startAnimation(alphaAnimation);
动画叠加AnimationSet
AnimationSet animationSet = new AnimationSet(true);
animationSet.setDuration(2000);
animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(rotateAnimation1);
animationSet.addAnimation(translateAnimation1);
animationSet.addAnimation(scaleAnimation1);
imageView.startAnimation(animationSet);
通过AnimationSet的addAnimation方法可将各动画添加进来,然后设置时间即可。
动画监听
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// 动画开始时
}
@Override
public void onAnimationEnd(Animation animation) {
// 动画结束时
}
@Override
public void onAnimationRepeat(Animation animation) {
// 重复动画时
}
});
我们常常会在动画执行的开始或者结束时做一些相应的操作,此时就会需要监听动画的执行过程。如一个动画之后执行另一个动画等。
属性动画(Property Animation)
Android 3.0之后引进了属性动画,相较于之前的动画,属性动画真实改变了View的属性,而之前的那两种动画则只是视图上的改变,其触发位置仍旧没有变化,因此属性动画更适合做一些需要交互较强的动画。
ObjectAnimator
属性动画常用到的一个类就是ObjectAnimator,它是属性动画中最重要的一个执行类,继承自属性动画中最重要的类ValueAnimator。ObjectAnimator使用起来也相对较方便,只要记得第二个参数所要执行的动画即可,因为这里所填的是第一个参数对象的属性,且这个属性必须有get、set方法。我们以一个简单的用法来说明
ObjectAnimator objectAnimator =ObjectAnimator.ofFloat(imageView,"translationX",-300,300);
objectAnimator.setDuration(2000);
// 设置播放次数。如果为ValueAnimator.INFINITE表示无限播放下去
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
// 设置播放重复模式。RESTART表示重新开始动画,REVERSE表示动画反过来播放。
objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
objectAnimator.start();
通过ObjectAnimator的工厂方法创建对象,其中第一个参数是要操作的View,第二个参数是要操作的属性,第三个参数是对应的属性变化,其是一个可变数组,即后面可继续添加参数,如这个效果就是将imageView控件在X轴方向的位移。第三个参数及其之后的参数为关键转折点。重点说下第二个参数,我们对View的动画操作常用的有(即第二个参数的值):
pivotX和pivotY:设置View的支点位置,默认为View的中心点
translationX和translationY:View的X轴和Y轴的偏移量,或者说位移距离。
rotation、rotationX和rotationY:View围绕支点,分别以Z轴、X轴和Y轴旋转,其中Z轴垂直屏幕。
scaleX和scaleY:View以支点做缩放。
x和y:直接设置View的位置,相当于原位置移动x和y距离。
alpha:设置View的透明度,1不透明,0全透明。
那么之前所说的set和get和这些值有什么关系呢?我们会有这些值,是因为内部通过java反射机制来调用set函数修改对象的属性值,我们打开源码ImageView继承View,而View中有一系列的函数,即方法。我们可以发现这些可以改变View属性的方法,如setTranslationX/getTranslationX、setRotationX/getRotationX等,这才使得属性动画能够找到这些方法并对其属性值做更改。
PropertyValuesHolder
PropertyValuesHolder使用起来和ObjectAnimator一样,只是它可以通过ObjectAnimator的ofPropertyValuesHolder方法将各个效果叠加起来,和AnimationSet类似。其使用方法如下,效果是在移动同时进行缩放操作。
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX",200f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX",1f,0f,1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY",1f,0f,1f);
ObjectAnimator.ofPropertyValuesHolder(imageView,pvh1,pvh2,pvh3).setDuration(2000).start();
ValueAnimator
ValueAnimator是属性动画中最重要的一个类,继承自Animator。它定义了属性动画中大部分的功能,如计算各帧的属性值、处理更新事件、根据属性值得类型知道操作的属性对象而做相应的计算规则的控制。也就是说ValueAnimator其实本身不负责动画的执行,更重要的是动画执行过程中数据的获取。可以根据需要设置动画持续的时间、插值方式、重复次数等,然后启动动画。使用时一般需要注册AnimatorUpdateListener监听器。然后再监听器中获取实时数值,之后将得到的数值设置到需要的地方。用法大致如下:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,100);
valueAnimator.setTarget(imageView);
valueAnimator.setDuration(2000);
valueAnimator.start();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float value = (Float) animation.getAnimatedValue();
// TODO 使用获取来的值
}
});
AnimatorSet
视图动画有同感AnimationSet将各个动画叠加,属性动画当然也有,那就是AnimatorSet,相较于之前的PropertyValuesHolder,AnimatorSet可以更好地控制各个动画的顺序,如用AnimatorSet实现如上面PropertyValuesHolder一样的动画效果
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(imageView,"translationX",200f);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(imageView,"scaleX",1f,0f,1f);
ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(imageView,"scaleY",1f,0f,1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(2000);
animatorSet.playTogether(objectAnimator1,objectAnimator2,objectAnimator3);
animatorSet.start();
在此AnimatorSet还提供不同的方法来调整各个属性动画的顺序。如:
// 三个动画同时播放
animatorSet.playTogether(objectAnimator1,objectAnimator2,objectAnimator3);
// 三个动画按顺序播放
animatorSet.playSequentially(objectAnimator1,objectAnimator2,objectAnimator3);
// objectAnimator1动画与objectAnimator2动画同时播放
animatorSet.play(objectAnimator1).with(objectAnimator2);
// objectAnimator1动画在objectAnimator2动画播放之前进行,即先播放1动画再播放2动画
animatorSet.play(objectAnimator1).before(objectAnimator2);
// objectAnimator1动画在objectAnimator2动画播放之后进行,即先播放2动画再播放1动画
animatorSet.play(objectAnimator1).after(objectAnimator2);
animate方法给View设置动画
在系统3.0之后,如果动画只执行一次,则可以考虑使用animate()方法来完成动画。即View调用animate()。其简单使用例子如下。
imageView.animate()
.translationX(200f)
.scaleX(2f).scaleY(2f)
.setDuration(2000)
.withStartAction(new Runnable() {
@Override
public void run() {
// 动画开始
}
}).withEndAction(new Runnable() {
@Override
public void run() {
// 动画结束
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
});
这里调用animate()方法实际是活的一个ViewPropertyAnimator对象,其实际也是属性动画。但这里的属性不是可变数组,且不能设置重复播放。因此用于简单动画叠加或者一次性播放的还是挺方便的。
动画事件的监听
在属性动画执行的过程中,如果需要在某个执行过程做其他操作,则需添加监听事件
objectAnimator.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) {
// 动画重复时
}
});
如果只想单独监听某个事件则注册监听AnimatorListenerAdapter即可选择需要的事件对其进行监听。
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
@Override
public void onAnimationRepeat(Animator animation) {
super.onAnimationRepeat(animation);
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
}
@Override
public void onAnimationPause(Animator animation) {
super.onAnimationPause(animation);
}
@Override
public void onAnimationResume(Animator animation) {
super.onAnimationResume(animation);
}
});
XML中定义属性动画
在res目录下创建anim或是animator文件夹,并新建一个根标签为objectAnimator的xml文件
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:propertyName="scaleX"
android:valueFrom="1.0"
android:valueTo="2.0"
android:valueType="floatType">
</objectAnimator>
在代码中使用
Animator animator = AnimatorInflater.loadAnimator(this,R.animator.objectanim);
animator.setTarget(imageView);
animator.start();
这里重点讲的篇代码中的使用,XML中其实都差不多,只要记住各个属性的含义即可。