Android PropertyAnimation 属性动画(二)弹跳小球实例
前言
上篇博客简单介绍了属性动画的原理,这篇博客将会以一个简单的实例来运用上之前讲的内容,并对Animator的几个回调方法进行讲解。
目标是自定义一个View,在画布上绘制一个小球,点击屏幕后小球从顶部自由下落,落到底边后反弹,反弹损失一半的能量,也就是说小球只能上升到下落时一半的高度,再重复这个过程直到退出程序。如图:

创建自定义View
首先我们要创建一个自定义View,这里我就采用继承LinearLayout的方式来创建这个View,但要注意LinearLayout默认是不绘制自身的,需要在onDraw()方法之前适当的时候调用setWillNotDraw(false);令其进行绘制。
在继承LinearLayout的同时我们要实现全部三个构造方法,否则xml文件的预览解析会出现问题:
1 | public VView(Context context) { |
创建好自定义View后,我们就可以在对应的layout xml布局文件中用完整包名+类名的方式使用我们的自定义View:
1 | <com.viseator.viewtest.VView |
同时,我们在绘制之前的onMeasure()方法中调用setWillNotDraw(false);使自定义View可以绘制:
1 |
|
这里也调用了setOnClickListener()注册之后的点击事件。
绘制
小球的绘制
1 | private ValueAnimator animator; |
这里第10行对是否是第一次绘制进行判断并将画布大小保存到canvasHeight供之后的绘制使用(之后的绘制的坐标需要相对于画布的坐标)并设置paint的属性。
drawCircle()方法也非常简单,只是调用canvas提供的drawCircle()方法指定位置与半径和之前设置的paint,调用后就会在屏幕上的对应位置绘制一个小球。
下落动画的绘制
下面就要让小球“动”起来,其实并不是小球发生了移动,只是我们不停地改变小球绘制的位置,当绘制的速率(帧率)大于24帧时的,就在视觉上变成了流畅的动画。也就是说,我们需要使用Animator连续地改变小球的位置,为了实现加速的效果,位置的改变速率应该随时间增加,也就是需要我们上一篇博客提到的Evaluator来实现。
animator的初始化
1 | void init(int start, int end) { |
写成一个初始化方法便于重新初始化。
第2行将传入的值区间的开始与结束值作为参数获得了一个值为int的ValueAnimator。
第3行设置了动画的时间为1秒。
第4、5行分别设置了动画的重复次数为无限次,重复模式为重新开始,顾名思义,动画可以重复进行,重新开始的重复模式意味着一次动画结束之后数值重新从start到end进行改变,也可以设置重复的模式为反向,即一次动画结束之后数值从end到start变化。
第六行为animator设置了一个库中提供的AccelerateInterpolator即加速插值器,这就是我们实现加速效果的关键,上篇之中已经看过它的源码,默认时返回的最终动画进行百分比是时间百分比的平方,达到了位置随着时间的平方变化,也就是实现了加速下落的效果。
第7、8两行分别为animator设置了一个UpdateListener用于监听数值变化,一个Listener用于监听animator本身开始、停止、重复。
完成下落动画
创建好了ValueAnimator,下一步就是在适合的时候在画布上重新绘制位置参数被animator改变后的小球。注意到我们之前小球的y坐标存储在yPos变量中,我们只要适时令yPos等于改变后的值再通过invalidate()方法进入onDraw()方法让View按小球的参数重新进行绘制就可以了。
animator的ValueAnimator.AnimatorUpdateListener为我们提供了一个及时刷新View的时机,之前为animator注册一个UpdateListener之后,每当animator的值发生改变时,onAniamtionUpdate()就会被回调。
那我们就可以在这个回调方法中为yPos设置新的值并令View重新绘制:
1 |
|
这样,我们只要启动animator令它的值开始变化,就会不断地调用onAnimationUpdate()重绘View:
1 |
|
start()方法令animator开始。
到这里,我们已经可以看到点击屏幕后小球下落到底部并停止的效果。
回弹效果实现
我们之前已经为animator设置了无限重复,并且模式为重新开始,那么要做到回弹的效果,就要在小球落到底边(动画完成)之后,为小球设置新的初始值与最终值,让小球从最低点回到落下时一半的高度。高度数据我们在onClick()中的第4行(上面代码)已经初始化为了相对于画布的高度,之后再使用时只需把它除2就可以表示圆心距底边的高度了。
Animator.AnimatorListener为我们提供了一系列方法用于监听animator状态的变化(而不是数值):

(除金色为Android 8新增外)依次为动画取消,动画结束,动画开始重复,动画开始。
这里我们就需要在onAnimationReapt()回调中为动画设置新的初值与结束数值:
1 |
|
回调参数中的animation就是回调这个函数的animator,第3行对其进行一个类型转换。
这里我们使用了一个isDown参数来判断是否是下落过程,如果上个动画是下落过程,就将animationHeight减半。
第7行把isDown置反,再根据isDown的判断使用setIntValues()方法为animator设置新的范围,使用setInterpolator()方法设置新的插值器,注意上升时使用的应该是DecelerateInterpolater减速上升。
这样在新的动画开始时属性改变的范围就得到了改变,也就使得小球可以反弹了。
为了让每一次点击时动画都可以重新开始,在onClick()方法中加入几行初始化代码:
1 |
|
这里第3-5行让如果存在的animator停止,否则新动画无法启动。
下篇博客将会从源码角度继续探索animator的实现原理和更高级的一些特性。