前言:前两天看了下郭大神写的属性动画,感觉挺好,所以就很不要脸的搬过来了,附上原文地址:Android属性动画完全解析(上),初识属性动画的基本用法,有兴趣的可以看看
下面我们就来玩玩属性动画吧
其实属性动画是使用相当简单,我们就先从最简单的操作开始吧,既然属性动画的运行机制是通过不断地对值进行操作来实现的,那么我们就来做一个 0 到 1 值的过渡吧
ValueAnimator anim = ValueAnimator.ofFloat(0f,1f);
anim.setDuration(500);
anim.start();
就这么几行代码,其实我们就已经实现了从0到一的值的过渡,而构建ValueAnimator的实例很简单,我们只需要调用ValueAnimator的ofFloat()方法就可以完成,这里传入的 0 和 1 就表示我们设置的初始值 和 结束值,然后就是设置动画的运行时长setDuration()以及启动动画start();
用法就是这么简单,但是问题来了就这么几行代码我们怎么才能知道这个动画运行了呢?我们怎么才能获取到运行期间的过渡值呢?不用想 Android API中肯定给我们提供了能够访问过渡值的方法(而且很有可能是一个监听器,为什么呢? 因为监听就是用来监听变化的,既然值发生了变化,那么Android API肯定需要通过回调来告诉我们,使用监听器是必然的),下面我们可以试着来找一下:
在ValueAnimator这个类中有一个 addUpdateListener(ValueAnimator.AnimatorUpdateListener listener) 这么一个方法,那么我们就来试一下,是不是 试一下才知道:
来吧 代码走起来:
//创建ValueAnimator实例
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedValue = (float) animation.getAnimatedValue();
Log.d("TAG","current value is = "+animatedValue);
}
});
valueAnimator.setDuration(500);
valueAnimator.start();
可以看到500毫秒内 我们设置的值确实是从 0 过渡到了 1,这就说明ValueAnimator确实是帮我们完成了值的过渡,但是ValueAnimator能做得操作真的只有这么简单么?当然不是的,复杂的逻辑我们后面慢慢将,我们先来看看ValueAnimator的ofFloat()方法吧,细心的同学可能已经发现了,onFloat()方法中接收的参数是 一个 float类型的可变数组,那也就是说,我们可以传递多个值咯,来试一下,看看效果:
这里我在ofFloat()中传递的参数依次是 0f、1f、3f、0f
current value is = 0.0
current value is = 0.0
current value is = 0.10287103
current value is = 0.40194967
current value is = 0.8491747
current value is = 1.1114464
current value is = 1.3324375
current value is = 1.5573113
current value is = 1.7713349
current value is = 2.0
current value is = 2.228666
current value is = 2.4426885
current value is = 2.6675625
current value is = 2.8885536
current value is = 2.862234
current value is = 2.5475242
current value is = 2.244172
current value is = 1.9706244
current value is = 1.6942958
current value is = 1.4342914
current value is = 1.2058493
current value is = 0.9817581
current value is = 0.7781377
current value is = 0.6062565
current value is = 0.44563937
current value is = 0.30861282
current value is = 0.20191145
current value is = 0.11282444
current value is = 0.049262524
current value is = 0.013091326
current value is = 0.0
如果在使用过程中你想要得不是float类型的数据,那也容易,可以使用ofInt()来获取整形数据,这里我们就不再列出了,有兴趣的可以自己了解下
那么除此之外,ValueAnimator还给我们提供了延时播放动画的方法setStartDelay(),我们也可以通过setRepeatMode()方法来设置动画的播放次数以及循环模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放以及倒序播放,大家可以自己试一下
相对于ValueAnimator,ObjectAnimator才是我们最经常接触的类,因为ValueAnimator仅仅是对值进行平滑的过渡,也就只有我们需要动态改变某个值的时候才会用到,在做动画是我们使用的还是ObjectAnimator,ObjectAnimator是可以对任意对象的任意属性进行操作的
不过虽然说ObjectAnimator比较常用,但是其实ObjectAnimator是继承自ValueAnimator的,底层的动画实现机制其实使用的还是ValueAnimator,而ObjectAnimator可以让我们更加方便的完成动画操作,那么既然是继承自ValueAnimator,说明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,比如我们想要实现一个 textview在5秒内从常规变成全透明,在从全透明变成常规,那么我们就可以这么写:
//首先和ValueAnimator一样,先获取ObjectAnimator的实例
TextView textView = (TextView) findViewById(R.id.tv_content);
Button start = (Button) findViewById(R.id.bt_start);
alpha = ObjectAnimator.ofFloat(textView, "alpha", 1f, 0f, 1f);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
alpha.setDuration(5000).start();
}
});
可以看到我们这里调用的还是ofFloat()方法,只不过传递的参数变了,使用ObjectAnimator的ofFloat时需要我们我们多传入两个参数,第一个参数要求我们传入一个Object对象,表示的是我们要操作的动画的对象,也就是我们想要对那个对象进行动画操作这里就传入那个对象,这里我们是要对Textview进行渐变操作,所以这里传入的是textview,第二个参数要求我们传入一个字符串,其实表示的就是对象的属性,前面已经讲过,属性动画其实操作的就是对象的属性,这里我们传入”alpha”,为什么会传这么一个参数呢?有什么代表意义么?后面我们会在列出几个常见的例子,到时候再来说说第二个参数传递的要求吧,我们先看下效果图:
学会了这个之后,是不是相当 so easy, 那么我们在来试试旋转吧,我们可以先让 Textview旋转个180度,然后在旋转回来,看代码:
//首先和ValueAnimator一样,先获取ObjectAnimator的实例
TextView textView = (TextView) findViewById(R.id.tv_content);
Button start = (Button) findViewById(R.id.bt_start);
alpha = ObjectAnimator.ofFloat(textView, "rotation", 0f, 180f, 0f);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
alpha.setDuration(5000).start();
}
});
可以看到我们其实没怎么改代码,就是变了个属性而已,改了下动画的样式,让动画从0段旋转到180度,然后在旋转会0度,看下效果是怎样的:
由于gif是用GIFCam录制的,所以可能有点延迟,大家多多包涵
那么如果是想要将textview移出屏幕,然后在移回来呢?
//首先和ValueAnimator一样,先获取ObjectAnimator的实例
TextView textView = (TextView) findViewById(R.id.tv_content);
Button start = (Button) findViewById(R.id.bt_start);
//获取到当前view的位置,如果你的textview的布局是match_parent 那么获取的值将为0.0
float translationX = textView.getTranslationX();
Toast.makeText(this,"translationX = "+translationX,Toast.LENGTH_LONG).show();
alpha = ObjectAnimator.ofFloat(textView, "translationX", translationX, -500, translationX);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
alpha.setDuration(5000).start();
}
});
这里我们首先调用了textview的getTranslationX()来获取当前的textview的translationX,保证我们在移回屏幕时能回到原来的位置,第二个参数我们传入的是 translationX,其实就是在告诉系统我们想要的效果是在 X轴上实现,运行下代码,效果如下:
现在渐变、旋转、平移我们都讲完了,就剩下一个缩放,来看代码:
//首先和ValueAnimator一样,先获取ObjectAnimator的实例
TextView textView = (TextView) findViewById(R.id.tv_content);
Button start = (Button) findViewById(R.id.bt_start);
alpha = ObjectAnimator.ofFloat(textView, "scaleY", 1, 3, 1,3);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
alpha.setDuration(5000).start();
}
});
这里主要看第二个参数,scaleY 表示在垂直方向上进行缩放,运行下代码,效果显示如下:
到目前为止,ObjectAnimator的基本操作就到此为止了,想必大家一直有个疑惑,就是我们在使用ofFloat()时,我们传递的第二个参数到底是什么意思呢?目前我们已经使用了 alpha、rotation、translationX、scaleY这几个值,已经分别完成了渐变、旋转、平移、缩放等多个动画,那么ofFloat的传值有什么要求么?其实这个还真不好回答,可以说的是ofFloat()的第二个参数可以接受任意的字符串,很意外吧?其实前面我们提到过,属性动画的引入并不是仅仅针对view来进行设计的,它所负责的操作就是对某个对象的某个属性进行不断的赋值操作,说到这里可能大家也应该能够想到ObjectAnimator到底是怎么工作的了,下面我们就来具体的分析下
其实ObjectAnimator工作原理很简单,既然他是通过ValueAnimator对值的过渡完成的,那么就好理解了,比如说我们前面举得例子:
alpha = ObjectAnimator.ofFloat(textView, "alpha", 1f, 0f, 1f);
为什么我们设置了 “alpha”后就能改变view的透明度呢?其实很好理解,肯定是ObjectAnimator内部通过获取过渡值然后 指定的 对象的 alpha 这一属性 进行了动态的赋值,从而改变了指定对象的透明度,但是真的是如此吗?我们可以去textview中去找找有没有 “alpha” 这一个属性,在这里我已经找过了,很遗憾的告诉大家没有,不过textview中没有,就连textview的祖类 View 中也是没有 “alpha”这个属性的,既然没有,那么他是怎么动态改变指定对象的 “alpha”的呢? 其实大家想多了,我们在开发中想要对一个对象的某个属性进行赋值时,使用的时什么方法?是不是setXXX()、getXXX(),想明白了没?其实view之所以可以改变透明度,使用的就是setAlpha()这个方法,现在大家总应该明白了吧! ObjectAnimator内部会根据我们提供的属性值,去找对应的get和set方法,完成的属性的改变,那么View中肯定存在 setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()这些方法,不信的话大家可以去找一下
前面我们已经搞定了属性动画的基本用法,但是这还没完,因为有时候仅仅单个动画是实现不了我们现实生活中想要实现的效果的,所以将多个动画组合在一起时尤其的重要,幸运的是,Android团队在设计属性动画的时候也充分考虑到了组合动画的功能,然后也提供了一套API来让我们将多个动画组合到一起
要想完美的将多个动画组合到一起,就必须使用AnimatorSet这个类来实现了,这个类中提供了一个 play()方法,供我们传入一个Animator(ValueAnimator/ObjectAnimator)将会给我们返回一个Animator.Builder的实例,而Animator的Builder对象中包含四个方法:
好了,有了这四个动画,我们就可以完成组合动画的逻辑了,比如有这么一个效果:我们现在想要让textview先从屏幕外移动到屏幕内,然后开始旋转360度,旋转的同时完成淡入淡出的操作,这个逻辑怎么实现呢?代码就可以这样写:
//首先和ValueAnimator一样,先获取ObjectAnimator的实例
TextView textView = (TextView) findViewById(R.id.tv_content);
Button start = (Button) findViewById(R.id.bt_start);
//定义一个从屏幕外移动到屏幕里的动画,首先需要获取textview的translationX
float translationX = textView.getTranslationX();
translation = ObjectAnimator.ofFloat(textView, "translationX", -500,translationX);
//定义一个旋转360的textview动画
rotation = ObjectAnimator.ofFloat(textView, "rotation",0,360);
//定义一个淡入淡出的textview动画
alpha = ObjectAnimator.ofFloat(textView, "alpha", 1f,0f,1f);
//定义一个动画的集合
animatorSet = new AnimatorSet();
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开始编写动画的执行顺序,rotation 和 alpha动画插入到传入的动画(translation)之后执行,
// 因为alpha是通过with进行连接的,所以alpha动画会和rotation一起执行
animatorSet = new AnimatorSet();
animatorSet.play(rotation).with(alpha).after(translation);
animatorSet.setDuration(3000);
animatorSet.start();
}
});
是不是很简单,动画还是一样的动画,只不过我们将所有的动画管理起来了,交给了AnimationSet去控制他们的执行顺序,来看下效果:
监听器其实是动画中我们经常要用到的东西,而Animation监听器是用来干嘛的呢?我们动画是可以执行了,但是动画到底什么时候结束,什么时候开始,这些状态我们怎么知道呢?这就需要用到Animation监听器了
大家都已经知道,ObjectAnimator是继承自ValueAnimator的,而ValueAnimator又是继承自Animator的,因此不管是ValueAnimator还是ObjectAnimator都是可以使用addListener()这个方法的,另外AnimatorSet也是继承自Animator的,因此addListener()这个方法算是个通用的方法:
添加一个监听器的代码如下:
anim.addListener(new AnimatorListener() {
02. @Override
03. public void onAnimationStart(Animator animation) {
04. }
05.
06. @Override
07. public void onAnimationRepeat(Animator animation) {
08. }
09.
10. @Override
11. public void onAnimationEnd(Animator animation) {
12. }
13.
14. @Override
15. public void onAnimationCancel(Animator animation) {
16. }
17.});
可以看到,我们需要实现的方法有四个,onAnimationStart()方法会在动画开始的时候调用,onAnimationRepeat()方法会在动画重复时调用,onAnimationEnd()方法会在动画执行结束后调用,onAnimationCancel()方法会在动画被取消的时候调用。
但是也许有很多时候我们并不想实现这么多的方法,可能只是想要监听动画结束的那个方法,那么如果实现AnimatorListener这个借口,那么我们就不得不实现其他的三个方法,这样就显得非常繁琐,因此,Android API给我们提供了一个适配器,叫做AnimatorListenerAdapter,使用这个类就可以解决掉实现多余方法的繁琐了,如下所示:
anim.addListener(new AnimatorListenerAdapter() {
02.});
所以这个时候我们只需要实现我们想要实现的方法就可以了
anim.addListener(new AnimatorListenerAdapter() {
02. @Override
03. public void onAnimationEnd(Animator animation) {
04. }
05.});
玩过补间动画的朋友肯定都用过XML来实现补间动画的效果,那么既然补间动画可以使用XML布局来实现,那么作为属性动画,使用XML布局实现动画效果那是必须的。
只不过使用XML布局来编写动画相对于代码编写的属性动画可能在效率上会比较慢一点,但在重用方面比较方便,比如将某个通用的XML布局动画抽取到一个布局文件中,那么在整个项目中我们都可以使用这个布局动画
如果想要使用布局动画,首先需要我们在res下的drawable文件夹下新建一个animator文件夹,所有关于动画的布局文件都应该放在这个文件夹下,然后使用属性动画我们可以在animator文件夹下使用三种标签:
那么比如说我们想要实现一个从0到100平滑过渡的动画,在XML当中就可以这样写:
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="100"
android:valueType="intType"/>
而如果我们想将一个视图的alpha属性从1变成0,就可以这样写:
01.<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="alpha"/>
其实XML编写动画在可读性方面还是挺高的,上面的内容相信不用我做解释大家也都看得懂吧。
另外,我们也可以使用XML来完成复杂的组合动画操作,比如将一个视图先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作,就可以这样写:
<set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="sequentially" >
<objectAnimator android:duration="2000" android:propertyName="translationX" android:valueFrom="-500" android:valueTo="0" android:valueType="floatType" >
</objectAnimator>
<set android:ordering="together" >
<objectAnimator android:duration="3000" android:propertyName="rotation" android:valueFrom="0" android:valueTo="360" android:valueType="floatType" >
</objectAnimator>
<set android:ordering="sequentially" >
<objectAnimator android:duration="1500" android:propertyName="alpha" android:valueFrom="1" android:valueTo="0" android:valueType="floatType" >
</objectAnimator>
<objectAnimator android:duration="1500" android:propertyName="alpha" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" >
</objectAnimator>
</set>
</set>
</set>
这段XML实现的效果和我们刚才通过代码来实现的组合动画的效果是一模一样的,每个参数的含义都非常清楚,相信大家都是一看就懂,我就不再一一解释了。
最后XML文件是编写好了,那么我们如何在代码中把文件加载进来并将动画启动呢?只需调用如下代码即可:
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);
animator.setTarget(view);
animator.start();
调用AnimatorInflater的loadAnimator来将XML动画文件加载进来,然后再调用setTarget()方法将这个动画设置到某一个对象上面,最后再调用start()方法启动动画就可以了,就是这么简单。
好的,通过本篇文章的学习,我们对属性动画已经有了颇为深刻的认识,那么本篇文章的内容到此为止,下篇文章当中将会介绍更多关于属性动画的其它技巧,感兴趣的朋友请继续阅读 Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法
。
相关文章:
1、 Android 属性动画(Property Animation) 完全解析 (上)
2、Android 属性动画(Property Animation) 完全解析 (下)