android中动画分为3种:
1.Tween Animation:
补间动画(tweened animation)通过对场景里的对象不断做图像变换(平移、缩放、旋转)产生动画效果,即是一种渐变动画;
缺点 :补间动画是只能够作用在View上;它只能够实现移动、缩放、旋转和淡入淡出这四种动画操作;它只是改变了View的显示效果而已,而不会真正去改变View的属性。它并不能改变事件响应的位置,它只是单纯地修改了显示。比如说实现一个按钮的移动,那么按钮的实际点击有效区依然在原来的地方,点击移动后的地方是不会有点击事件发生的。
Tween Animation有四种形式:
alpha 渐变透明度动画效果
scale 渐变尺寸伸缩动画效果
translate 画面位置移动动画效果
rotate 画面旋转动画效果
这四种动画实现方式都是通过Animation类和AnimationUtils配合实现。
参数详细解释见Android Animations动画使用详解
2.Frame Animation:
顺序播放事先做好的图像,是一种画面转换动画。帧动画(frame-by-frame animation)的工作原理很简单,其实就是将一个完整的动画拆分成一张张单独的图片,然后再将它们连贯起来进行播放,类似于动画片的工作原理。
3.Property Animation:
属性动画,通过动态地改变对象的属性从而达到动画效果,属性动画为API 11新特
新引入的属性动画机制已经不再是针对于View来设计的了,也不限定于只能实现移动、缩放、旋转和淡入淡出这几种动画操作,同时也不再只是一种视觉上的动画效果了。它实际上是一种不断地对值进行操作的机制,并将值赋值到指定对象的指定属性上,可以是任意对象的任意属性
Android 属性动画:
Android属性动画完全解析(上),初识属性动画的基本用法
Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法
Android属性动画完全解析(下),Interpolator和ViewPropertyAnimator的用法
Android 属性动画(Property Animation) 完全解析 (上)
ValueAnimator
ValueAnimator是整个属性动画机制当中最核心的一个类,ObjectAnimator也是继承自ValueAnimator。前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。
ValueAnimator当中最常用的应该就是ofFloat()和ofInt()这两个方法了,另外还有一个ofObject()方法。
那么除此之外,我们还可以调用setStartDelay()方法来设置动画延迟播放的时间,调用setRepeatCount()和setRepeatMode()方法来设置动画循环播放的次数以及循环播放的模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放和倒序播放的意思。
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()方法来添加一个动画的监听器,在动画执行的过程中会不断地进行回调,这里用来监听数值的变化。
前面我们使用过了ValueAnimator的ofFloat()和ofInt()方法,分别用于对浮点型和整型的数据进行动画操作的,但实际上ValueAnimator中还有一个ofObject()方法,是用于对任意对象进行动画操作的。但是相比于浮点型或整型数据,对象的动画操作明显要更复杂一些,因为系统将完全无法知道如何从初始对象过度到结束对象,因此这个时候我们就需要实现一个自己的TypeEvaluator来告知系统如何进行过度。
下面来先定义一个Point类,如下所示:
public class Point {
private float x;
private float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
Point类非常简单,只有x和y两个变量用于记录坐标的位置,并提供了构造方法来设置坐标,以及get方法来获取坐标。接下来定义PointEvaluator,如下所示:
public class PointEvaluator 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 = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
Point point = new Point(x, y);
return point;
}
}
可以看到,PointEvaluator同样实现了TypeEvaluator接口并重写了evaluate()方法。其实evaluate()方法中的逻辑还是非常简单的,先是将startValue和endValue强转成Point对象,然后同样根据fraction来计算当前动画的x和y的值,最后组装到一个新的Point对象当中并返回。
这样我们就将PointEvaluator编写完成了,接下来我们就可以非常轻松地对Point对象进行动画操作了,比如说我们有两个Point对象,现在需要将Point1通过动画平滑过度到Point2,就可以这样写:
Point point1 = new Point(0, 0);
Point point2 = new Point(300, 300);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);
anim.setDuration(5000);
anim.start();
代码很简单,这里我们先是new出了两个Point对象,并在构造函数中分别设置了它们的坐标点。然后调用ValueAnimator的ofObject()方法来构建ValueAnimator的实例,这里需要注意的是,ofObject()方法要求多传入一个TypeEvaluator参数,这里我们只需要传入刚才定义好的PointEvaluator的实例就可以了。
Object Animator
相比于ValueAnimator,ObjectAnimator可能才是我们最常接触到的类,因为ValueAnimator只不过是对值进行了一个平滑的动画过渡,但我们实际使用到这种功能的场景好像并不多。而ObjectAnimator则就不同了,它是可以直接对任意对象的任意属性进行动画操作的,比如说View的alpha属性。
由于ObjectAnimator继承自ValueAnimator,说明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,它们的用法也非常类似。
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();
第一个参数要求传入一个object对象,我们想要对哪个对象进行动画操作就传入什么,这里我传入了一个textview。第二个参数是想要对该对象的哪个属性进行动画操作,由于我们想要改变TextView的不透明度,因此这里传入"alpha"。后面的参数就是不固定长度了,想要完成什么样的动画就传入什么值,这里传入的值就表示将TextView从常规变换成全透明,再从全透明变换成常规。之后调用setDuration()方法来设置动画的时长,然后调用start()方法启动动画。
ofFloat()方法的第二个参数到底可以传哪些值呢?其实这里ObjectAnimator内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法,因此这里传入的名字其实无所谓的,只是一个指示而已。
不过,在使用ObjectAnimator的时候,有一点非常的重要,就是要操作的属性必须具有get、set方法,不然ObjectAnimator就无法起效。
常用的可以直接使用属性动画的属性包括:
(1)translationX和translationY:控制view从它布局容器左上角坐标偏移的位置;
(2)rotation、rotationX和rotationY:控制view围绕支点进行2D和3D旋转;
(3)scaleX和scaleY:控制view围绕着它的支点进行2D缩放;
(4)pivotX和pivotY:控制支点位置,围绕这个支点进行旋转和缩放处理。默认情况下,支点是view的中心点;
(5)x和y:控制view在它的容器中的最终位置,它是最初的左上角坐标和translationX、translationY的累计和;
(6)alpha:控制透明度,默认是1(不透明)。
如果一个属性不具有get和set方法,有两种解决的方法:一种是通过自定义一个属性类或者包装类,来间接地给这个属性增加get和set方法;第二种就是通过ValueAnimator来实现。
使用包装类的方法给一个属性增加set和get的方法,代码如下:
private static class WrapperView{
private View mTarget;
public WrapperView(View target){
mTarget = target;
}
public int getWidth(){
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
mTarget.getLayoutParams().width = width;
mTarget.requestFocus();
}
}
使用的时候只需要操作包装类就可以间接地调用get,set方法了
WrapperView wrapper = new WrapperView (mButton);
ObjectAnimator.ofInt(Wrapper,"width",500).setDuration(5000).start;
ObjectAnimator中的ofObject()方法同样是对任意对象进行动画操作。
ObjectAnimator内部的工作机制是通过寻找特定属性的get和set方法,然后通过方法不断地对值进行改变,从而实现动画效果的。因此我们就需要在MyAnimView中定义一个color属性,并提供它的get和set方法。那么接下来的问题就是怎样让setColor()方法得到调用了,毫无疑问,当然是要借助ObjectAnimator类,但是在使用ObjectAnimator之前我们还要完成一个非常重要的工作,就是编写一个用于告知系统如何进行颜色过度的TypeEvaluator。(具体使用见Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法)
组合动画AnimatorSet
独立的动画能够实现的视觉效果毕竟是相当有限的,因此将多个动画组合到一起播放就显得尤为重要。幸运的是,Android团队在设计属性动画的时候也充分考虑到了组合动画的功能,因此提供了一套非常丰富的API来让我们将多个动画组合到一起。
实现组合动画功能主要需要借助AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:
· after(Animator anim) 将现有动画插入到传入的动画之后执行
· after(long delay) 将现有动画延迟指定毫秒后执行
· before(Animator anim) 将现有动画插入到传入的动画之前执行
· with(Animator 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();
在属性动画中,AnimatorSet正是通过playTogether()、playSequentially()、animSet.play()、with()、before()、after()这些方法来控制多个动画的协同工作方式,从而做到对动画播放方式的精确控制。
Animator监听器
在很多时候,我们希望可以监听到动画的各种事件,比如动画何时开始,何时结束,然后在开始或者结束的时候去执行一些逻辑处理。这个功能是完全可以实现的,Animator类当中提供了一个addListener()方法,这个方法接收一个AnimatorListener,我们只需要去实现这个AnimatorListener就可以监听动画的各种事件了。
大家已经知道,ObjectAnimator是继承自ValueAnimator的,而ValueAnimator又是继承自Animator的,因此不管是ValueAnimator还是ObjectAnimator都是可以使用addListener()这个方法的。另外AnimatorSet也是继承自Animator的,因此addListener()这个方法算是个通用的方法。
添加一个监听器的代码如下所示:
anim.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
});
可以看到,我们需要实现接口中的四个方法,onAnimationStart()方法会在动画开始的时候调用,onAnimationRepeat()方法会在动画重复执行的时候调用,onAnimationEnd()方法会在动画结束的时候调用,onAnimationCancel()方法会在动画被取消的时候调用。
但是也许很多时候我们并不想要监听那么多个事件,可能我只想要监听动画结束这一个事件,那么每次都要将四个接口全部实现一遍就显得非常繁琐。没关系,为此Android提供了一个适配器类,叫作AnimatorListenerAdapter,使用这个类就可以解决掉实现接口繁琐的问题了,如下所示:
<span style="font-family:SimSun;font-size:12px;">1.anim.addListener(new AnimatorListenerAdapter() {
});</span>
这里我们向addListener()方法中传入这个适配器对象,由于AnimatorListenerAdapter中已经将每个接口都实现好了,所以这里不用实现任何一个方法也不会报错。那么如果我想监听动画结束这个事件,就只需要单独重写这一个方法就可以了,如下所示:
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
TypeEvaluator
可能在大多数情况下我们使用属性动画的时候都不会用到TypeEvaluator,但是大家还是应该了解一下它的用法,以防止当我们遇到一些解决不掉的问题时能够想起来还有这样的一种解决方案。
那么TypeEvaluator的作用到底是什么呢?简单来说,就是告诉动画系统如何从初始值过度到结束值。我们在上一篇文章中学到的ValueAnimator.ofFloat()方法就是实现了初始值与结束值之间的平滑过度,那么这个平滑过度是怎么做到的呢?其实就是系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值,我们来看一下FloatEvaluator的代码实现:
可以看到,FloatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法。evaluate()方法当中传入了三个参数,第一个参数fraction非常重要,这个参数用于表示动画的完成度的,我们应该根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了,用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了。
Interpolator
Interpolator这个东西很难进行翻译,直译过来的话是补间器的意思,它的主要作用是可以控制动画的变化速率,比如去实现一种非线性运动的动画效果。那么什么叫做非线性运动的动画效果呢?就是说动画改变的速率不是一成不变的,像加速运动以及减速运动都属于非线性运动。
TimeInterpolator接口已经有非常多的实现类了,这些都是Android系统内置好的并且我们可以直接使用的Interpolator。每个Interpolator都有它各自的实现效果,比如说AccelerateInterpolator就是一个加速运动的Interpolator,而DecelerateInterpolator就是一个减速运动的Interpolator。
<span style="font-family:SimSun;font-size:12px;">anim.setInterpolator(new AccelerateInterpolator(2f)); </span>
这里调用了setInterpolator()方法,然后传入了一个AccelerateInterpolator的实例,注意AccelerateInterpolator的构建函数可以接收一个float类型的参数,这个参数是用于控制加速度的。
编写自定义Interpolator:
最主要的难度都是在于数学计算方面的,因此这里也就写一个简单点的Interpolator来给大家演示一下。既然属性动画默认的Interpolator是先加速后减速的一种方式,这里我们就对它进行一个简单的修改,让它变成先减速后加速的方式。
新建DecelerateAccelerateInterpolator类,让它实现TimeInterpolator接口,代码如下所示:
public class DecelerateAccelerateInterpolator implements TimeInterpolator{
@Override
public float getInterpolation(float input) {
float result;
if (input <= 0.5) {
result = (float) (Math.sin(Math.PI * input)) / 2;
} else {
result = (float) (2 - Math.sin(Math.PI * input)) / 2;
}
return result;
}
}
ViewProperty Animator
ViewPropertyAnimator其实算不上什么高级技巧,它的用法格外的简单,只不过和前面所学的所有属性动画的知识不同,它并不是在3.0系统当中引入的,而是在3.1系统当中附增的一个新的功能,因此这里我们把它作为整个属性动画系列的收尾部分。
我们都知道,属性动画的机制已经不是再针对于View而进行设计的了,而是一种不断地对值进行操作的机制,它可以将值赋值到指定对象的指定属性上。但是,在绝大多数情况下,我相信大家主要都还是对View进行动画操作的。Android开发团队也是意识到了这一点,没有为View的动画操作提供一种更加便捷的用法确实是有点太不人性化了,于是在Android 3.1系统当中补充了ViewPropertyAnimator这个机制。
那我们先来回顾一下之前的用法吧,比如我们想要让一个TextView从常规状态变成透明状态,就可以这样写:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f);
animator.start();
我们要将操作的view、属性、变化的值都一起传入到ObjectAnimator.ofFloat()方法当中,虽然看上去也没写几行代码,但这不太像是我们平时使用的面向对象的思维。
那么下面我们就来看一下如何使用ViewPropertyAnimator来实现同样的效果,ViewPropertyAnimator提供了更加易懂、更加面向对象的API,如下所示:
textview.animate().alpha(0f);
果然非常简单!不过textview.animate()这个方法是怎么回事呢?animate()方法就是在Android 3.1系统上新增的一个方法,这个方法的返回值是一个ViewPropertyAnimator对象,也就是说拿到这个对象之后我们就可以调用它的各种方法来实现动画效果了,这里我们调用了alpha()方法并转入0,表示将当前的textview变成透明状态。
怎么样?比起使用ObjectAnimator,ViewPropertyAnimator的用法明显更加简单易懂吧。除此之外,ViewPropertyAnimator还可以很轻松地将多个动画组合到一起,比如我们想要让textview运动到500,500这个坐标点上,就可以这样写:
textview.animate().x(500).y(500);
可以看出,ViewPropertyAnimator是支持连缀用法的,我们想让textview移动到横坐标500这个位置上时调用了x(500)这个方法,然后让textview移动到纵坐标500这个位置上时调用了y(500)这个方法,将所有想要组合的动画通过这种连缀的方式拼接起来,这样全部动画就都会一起被执行。
那么怎样去设定动画的运行时长呢?很简单,也是通过连缀的方式设定即可,比如我们想要让动画运行5秒钟,就可以这样写:
textview.animate().x(500).y(500).setDuration(5000);
除此之外,本篇文章第一部分所学的Interpolator技术我们也可以应用在ViewPropertyAnimator上面,如下所示:
textview.animate().x(500).y(500).setDuration(5000)
.setInterpolator(new BounceInterpolator());
用法很简单,同样也是使用连缀的方式。相信大家现在都已经体验出来了,ViewPropertyAnimator其实并没有什么太多的技巧可言,用法基本都是大同小异的,需要用到什么功能就连缀一下,因此更多的用法大家只需要去查阅一下文档,看看还支持哪些功能,有哪些接口可以调用就可以了。
那么除了用法之外,关于ViewPropertyAnimator有几个细节还是值得大家注意一下的:
· 整个ViewPropertyAnimator的功能都是建立在View类新增的animate()方法之上的,这个方法会创建并返回一个ViewPropertyAnimator的实例,之后的调用的所有方法,设置的所有属性都是通过这个实例完成的。
· 大家注意到,在使用ViewPropertyAnimator时,我们自始至终没有调用过start()方法,这是因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成之后,动画就会自动启动。并且这个机制对于组合动画也同样有效,只要我们不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动。当然如果不想使用这一默认机制的话,我们也可以显式地调用start()方法来启动动画。
· ViewPropertyAnimator的所有接口都是使用连缀的语法来设计的,每个方法的返回值都是它自身的实例,因此调用完一个方法之后可以直接连缀调用它的另一个方法,这样把所有的功能都串接起来,我们甚至可以仅通过一行代码就完成任意复杂度的动画功能。