概述
Android系统提供了很多丰富的API去实现UI的2D与3D动画,最主要的划分可以分为如下几类:
View Animation: 视图动画在古老的Android版本系统中就已经提供了,只能被用来设置View的动画。
Drawable Animation: 这种动画(也叫Frame动画、帧动画)其实可以划分到视图动画的类别,专门用来一个一个的显示Drawable的resources,就像放幻灯片一样。
Property Animation: 属性动画只对Android 3.0(API 11)以上版本的Android系统才有效,这种动画可以设置给任何Object,包括那些还没有渲染到屏幕上的对象。这种动画是可扩展的,可以让你自定义任何类型和属性的动画。
View Animation
View Animation(视图动画),也叫Tween Animation(补间动画)可以在一个视图容器内执行一系列简单变换(位置、大小、旋转、透明度)。譬如,如果你有一个TextView对象,您可以移动、旋转、缩放、透明度设置其文本,当然,如果它有一个背景图像,背景图像会随着文本变化。
我们来看视图动画相关的类继承关系:
我们可以使用的类就是AlphaAnimation,RotateAnimation,ScaleAnimation,TranslateAnimation,AnimationSet。根据字面意思都很好理解:
- AlphaAnimation是透明度动画。
- RotateAnimation是旋转动画。
- ScaleAnimation是比例动画。
- TranslateAnimation是平移动画。
- AnimationSet就是动画的组合。
视图动画通过XML或Android代码定义,建议使用XML文件定义,因为它更具可读性、可重用性。
XML实现
首先,在res/anim/ 文件夹下定义动画实现alpha_anim.xml:
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toAlpha="0.0" />
然后,在Activity中:
Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.alpha_anim);
img = (ImageView) findViewById(R.id.img);
img.startAnimation(animation);
这样就可以实现ImageView透明度变化的动画。
也可以使用set标签将多个动画组合(代码源自Android SDK API):
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@[package:]anim/interpolator_resource"
android:shareInterpolator=["true" | "false"] >
<alpha
android:fromAlpha="float"
android:toAlpha="float" />
<scale
android:fromXScale="float"
android:toXScale="float"
android:fromYScale="float"
android:toYScale="float"
android:pivotX="float"
android:pivotY="float" />
<translate
android:fromXDelta="float"
android:toXDelta="float"
android:fromYDelta="float"
android:toYDelta="float" />
<rotate
android:fromDegrees="float"
android:toDegrees="float"
android:pivotX="float"
android:pivotY="float" />
<set>
...
</set>
</set>
可以看到AnimationSet可以把各种动画组合起来。
Java代码实现
有时候,动画的属性值可能需要动态的调整,这个时候使用xml 就不合适了,需要使用Java代码实现:
private void RotateAnimation() {
animation = new RotateAnimation(-deValue, deValue, Animation.RELATIVE_TO_SELF,
pxValue, Animation.RELATIVE_TO_SELF, pyValue);
animation.setDuration(timeValue);
if (keep.isChecked()) {
animation.setFillAfter(true);
} else {
animation.setFillAfter(false);
}
if (loop.isChecked()) {
animation.setRepeatCount(-1);
} else {
animation.setRepeatCount(0);
}
if (reverse.isChecked()) {
animation.setRepeatMode(Animation.REVERSE);
} else {
animation.setRepeatMode(Animation.RESTART);
}
img.startAnimation(animation);
}
这里animation.setFillAfter()决定了动画在播放结束时是否保持最终的状态;animation.setRepeatCount()和animation.setRepeatMode()决定了动画的重复次数及重复方式,具体细节可查看API。
各个动画属性的含义和用法结合动画自身的特点应该很好理解,在这里就不一一阐述了,详细的解释可以查看API。这里主要说一下interpolator和pivot。
Interpolator
Interpolator主要作用是可以控制动画的变化速率,就是动画进行的快慢节奏。
Android 系统已经为我们提供了一些Interpolator,比如 accelerate_decelerate_interpolator,accelerate_interpolator等。更多的interpolator 及其含义可以在Android SDK 中查看。同时这个Interpolator也是可以自定义的。
pivot
pivot决定了当前动画执行的参考位置。
pivot这个属性主要是在translate和scale动画中,这两种动画都牵扯到view的“物理位置“发生变化,所以需要一个参考点。而pivotX和pivotY就共同决定了这个点;它的值可以是float或者是百分比数值。
我们以pivotX为例:
- pivotX = 10,距离动画所在view自身左边缘10像素。
- pivotX = 10%,距离动画所在view自身左边缘的距离是整个view宽度的10%。
- pivotX = 10%p 距离动画所在view父控件左边缘的距离是整个view宽度的10%
注意
特别特别注意:补间动画执行之后并未改变View的真实布局属性值,切记这一点。
譬如我们在Activity中有一个Button在屏幕上方,我们设置了平移动画移动到屏幕下方然后保持动画最后执行状态呆在屏幕下方,这时如果点击屏幕下方动画执行之后的Button是没有任何反应的,而点击原来屏幕上方没有Button的地方却响应的是点击Button的事件。
Drawable Animation
Drawable Animation也叫Frame Animation(帧动画)是最容易实现的一种动画,这种动画更多的依赖于完善的UI资源,这种动画的实质其实是Drawable。
我们来看下类的继承结构:
所以这种动画的XML定义方式文件一般放在res/drawable/目录下。他的原理就是将一张张单独的图片连贯的进行播放,从而在视觉上产生一种动画的效果,有点类似于某些软件制作gif动画的方式。
举例说明
<!-- 注意:rocket.xml文件位于res/drawable/目录下 -->
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot=["true" | "false"] >
<item
android:drawable="@[package:]drawable/drawable_resource_name"
android:duration="integer" />
</animation-list>
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
rocketAnimation.start();
在代码中,我们会看到android:oneshot=”false”,这个oneshot的含义就是动画执行一次(true)还是循环执行多次。
这里其他几种动画实现的方式都是一样,无非就是图片资源的差异。
注意
特别注意,AnimationDrawable的start()方法不能在Activity的onCreate()方法中调运,因为AnimationDrawable还未完全附着到Window上,所以最好的调运时机是onWindowFocusChanged()方法中。
Property Animation
Android 3.0以后引入了属性动画,属性动画可以轻而易举的实现许多View动画做不到的事,上面也看见了,View动画无非也就做那几种事情,别的也搞不定,而属性动画就可以的,譬如3D旋转一张图片。其实说白了,你记住一点就行,属性动画实现原理就是修改控件的属性值实现的动画。
首先来看Animator的类继承关系:
Animator有两个实现类,AnimatorSet和ValueAnimator。前者顾名思义是动画的组合,后者是值动画。
ValueAnimator
ValueAnimator是整个属性动画机制当中最核心的一个类,前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。
但是ValueAnimator的用法却一点都不复杂,我们先从最简单的功能看起吧,比如说想要将一个值从0平滑过渡到1,时长300毫秒,就可以这样写:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.start();
怎么样?很简单吧,调用ValueAnimator的ofFloat()方法就可以构建出一个ValueAnimator的实例,ofFloat()方法当中允许传入多个float类型的参数,这里传入0和1就表示将值从0平滑过渡到1,然后调用ValueAnimator的setDuration()方法来设置动画运行的时长,最后调用start()方法启动动画。
用法就是这么简单,现在如果你运行一下上面的代码,动画就会执行了。可是这只是一个将值从0过渡到1的动画,又看不到任何界面效果,我们怎样才能知道这个动画是不是已经真正运行了呢?这就需要借助监听器来实现了,如下所示:
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();
可以看到,这里我们通过addUpdateListener()方法来添加一个动画的监听器,在动画执行的过程中会不断地进行回调,我们只需要在回调方法当中将当前的值取出并打印出来,就可以知道动画有没有真正运行了。
ObjectAnimator
相比于ValueAnimator,ObjectAnimator可能才是我们最常接触到的类,因为ValueAnimator只不过是对值进行了一个平滑的动画过渡,但我们实际使用到这种功能的场景好像并不多。而ObjectAnimator则就不同了,它是可以直接对任意对象的任意属性进行动画操作的,比如说View的alpha属性。
不过虽说ObjectAnimator会更加常用一些,但是它其实是继承自ValueAnimator的,底层的动画实现机制也是基于ValueAnimator来完成的,因此ValueAnimator仍然是整个属性动画当中最核心的一个类。那么既然是继承关系,说明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,它们的用法也非常类似,这里如果我们想要将一个TextView在5秒中内从常规变换成全透明,再从全透明变换成常规,就可以这样写:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();
可以看到,我们还是调用了ofFloat()方法来去创建一个ObjectAnimator的实例,只不过ofFloat()方法当中接收的参数有点变化了。这里第一个参数要求传入一个object对象,我们想要对哪个对象进行动画操作就传入什么,这里我传入了一个textview。第二个参数是想要对该对象的哪个属性进行动画操作,由于我们想要改变TextView的不透明度,因此这里传入”alpha”。后面的参数就是不固定长度了,想要完成什么样的动画就传入什么值,这里传入的值就表示将TextView从常规变换成全透明,再从全透明变换成常规。之后调用setDuration()方法来设置动画的时长,然后调用start()方法启动动画。
这里重点介绍一下第二个参数。textview对象中是不是有alpha属性这个值呢?没有,不仅textview没有这个属性,连它所有的父类也是没有这个属性的!这就奇怪了,textview当中并没有alpha这个属性,ObjectAnimator是如何进行操作的呢?其实ObjectAnimator内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法(如果想了解这个过程,请参看Android源码分析—属性动画的工作原理),因此alpha属性所对应的get和set方法应该就是:
public void setAlpha(float value);
public float getAlpha();
那么textview对象中是否有这两个方法呢?确实有,并且这两个方法是由View对象提供的,也就是说不仅TextView可以使用这个属性来进行淡入淡出动画操作,任何继承自View的对象都可以的。
当然我们可以在自定义View定义一个属性,并提供它的get和set方法。然后用属性动画对它进行操作。
ValueAnimator(当然包括ObjectAnimator)到底是怎样实现从初始值平滑过渡到结束值的呢?这个就是由TypeEvaluator和TimeInterpolator共同决定的。
具体来说,TypeEvaluator决定了动画如何从初始值过渡到结束值。
TimeInterpolator决定了动画从初始值过渡到结束值的节奏。
说的通俗一点,你每天早晨出门去公司上班,TypeEvaluator决定了你是坐公交、坐地铁还是骑车;而当你决定骑车后,TimeInterpolator决定了你一路上骑行的方式,你可以匀速的一路骑到公司,你也可以前半程骑得飞快,后半程骑得慢悠悠。
TypeEvaluator
前面说了,TypeEvaluator决定了动画如何从初始值过渡到结束值。这个TypeEvaluator是个接口,我们可以实现这个接口:
public class PointSinEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = (float) (Math.sin(x * Math.PI / 180) * 100) + endPoint.getY() / 2;
Point point = new Point(x, y);
return point;
}
}
PointSinEvaluator 继承了TypeEvaluator类,并实现了他唯一的方法evaluate;这个方法有三个参数,第一个参数fraction代表当前动画完成的百分比,这个值是如何变化的后面还会提到;第二个和第三个参数代表动画的初始值和结束值。这里我们的逻辑很简单,x的值随着fraction不断变化,并最终达到结束值;y的值就是当前x值所对应的sin(x) 值,然后用x和y产生一个新的点(Point对象)返回。
这样我们就可以使用这个PointSinEvaluator 生成属性动画的实例了:
Point startP = new Point(RADIUS, RADIUS);//初始值(起点)
Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//结束值(终点)
final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointSinEvaluator(), startP, endP);
valueAnimator.setRepeatCount(-1);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
postInvalidate();
}
});
这样我们就完成了动画轨迹的定义,现在只要调用valueAnimator.start() 方法,就会绘制出一个正弦曲线的轨迹。
TimeInterpolator
不过Interpolator并不是属性动画中新增的技术,实际上从Android 1.0版本开始就一直存在Interpolator接口了,而之前的补间动画当然也是支持这个功能的。因为所有的插补器都是实现了Interpolator这个接口,而Interpolator又是继承自一个叫做TimeInterpolator的接口(从3.0开始,增加了TimeInterpolator这个接口,并把原先的Interpolator接口的抽象方法移到了其中,3.0后的Interpolator接口也就什么也没做,只是对父类改了个名字,达到向下兼容)。
来看下类的继承关系:
可以看到,TimeInterpolator接口已经有非常多的实现类了,这些都是Android系统内置好的并且我们可以直接使用的Interpolator。每个Interpolator都有它各自的实现效果,比如说AccelerateInterpolator就是一个加速运动的Interpolator,而DecelerateInterpolator就是一个减速运动的Interpolator。
我们可以继承TimerInterpolator以自己的方式控制动画变化的节奏,也可以使用Android系统提供的Interpolator。我们可以通过setInterpolator()设置不同的Interpolator。
ViewPropertyAnimator
ViewPropertyAnimator其实算不上什么高级技巧,它的用法格外的简单,只不过和前面所学的所有属性动画的知识不同,它并不是在3.0系统当中引入的,而是在3.1系统当中附增的一个新的功能。
看一下它的用例:
textview.animate().x(500).y(500).setDuration(5000)
.setInterpolator(new BounceInterpolator());
用法很简单,相信大家现在都已经体验出来了,ViewPropertyAnimator其实并没有什么太多的技巧可言,用法基本都是大同小异的,需要用到什么功能就连缀一下,因此更多的用法大家只需要去查阅一下文档,看看还支持哪些功能,有哪些接口可以调用就可以了。
注意:
整个ViewPropertyAnimator的功能都是建立在View类新增的animate()方法之上的,这个方法会创建并返回一个ViewPropertyAnimator的实例,之后的调用的所有方法,设置的所有属性都是通过这个实例完成的。
大家注意到,在使用ViewPropertyAnimator时,我们自始至终没有调用过start()方法,这是因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成之后,动画就会自动启动。并且这个机制对于组合动画也同样有效,只要我们不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动。当然如果不想使用这一默认机制的话,我们也可以显式地调用start()方法来启动动画。
注意
在最后再提一下几个注意点:
1.如果在Activity退出时没有及时将动画停止,属性动画会导致Activity无法释放而导致内存泄漏,而补间动画却没有问题。因此,使用属性动画时切记在Activity执行onStop方法时顺便将动画停止。
2.使用帧动画时需要注意,不要使用过多特别大的图,容易导致内存不足。
3.开启ValueAnimator的时候会对线程是否有Looper做一个检查:
/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
* to true if called from the reverse() method.
*
* <p>The animation started by calling this method will be run on the thread that called
* this method. This thread should have a Looper on it (a runtime exception will be thrown if
* this is not the case). Also, if the animation will animate
* properties of objects in the view hierarchy, then the calling thread should be the UI
* thread for that view hierarchy.</p>
*
* @param playBackwards Whether the ValueAnimator should start playing in reverse.
*/
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
……
}
通过方法注释我们了解到:
必须在含有Looper的线程开启动画。
如果是改变View相关属性的动画,还必须是UI 线程,因为动画在哪个线程start()就在哪个线程执行。
参考:
1.Android应用开发之所有动画使用详解
2.Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法
3.Android 动画总结