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
的实现原理和更高级的一些特性。