4.2 属性动画
View动画框架唯一不好的地方就是没有交互性,当View发生动画后,它的显示位置虽然已经改变,但它的响应区域还是在动画前的位置,没有发生改变。所以Google就在Android 3.0以及其后的版本加入了属性动画框架,它丰富的API能帮助开发者轻松实现各种各样的动画效果。
下面首先介绍一下属性动画的原理,因为这有助于理解属性动画。
动画,其实就是把一个View控件平滑地过渡到某个位置。什么意思呢?例如现在要实现一个View控件的动画,将它向右移到50厘米的位置,如果没有加动画效果,那它将直接右移到50厘米处的位置,而如果加了动画效果,那它将是慢慢滑动地平移到50厘米处的位置,是一个渐变的过程。而这个滑动的过程其实就是View不断调用setTranslate(50)去更新自己位置的过程,实现代码如下:
上面这段代码就是动画的原理,通过不断更新View属性去实现动画效果。但是如果每次都要这样写一段长代码去实现移动View到某个位置的这么一个动画效果,这样也会很麻烦,每次都要去计算它要到达的位置距离是多少。所以,这时就需要属性动画框架了,因为它封装的API会自动做好这些烦琐的计算工作,开发者只需调用一行代码或者几行代码就能轻松地实现各种各样的动画效果。下面重点介绍属性动画实现的工具类。
(1)ViewPropertyAnimator
使用ViewPropertyAnimator去实现属性动画会很简单,因为它提供了很多方法,开发者只要直接使用就能实现动画效果,如图4.1所示。
所以,当想实现各种组合在一起的动画效果时,可以连缀使用:
图4.1 View方法及其对应的ViewPropertyAnimator方法
说到此处,要再讲一个概念,那就是插值器。插值器是什么?在Android中,插值就是根据时间流逝程度去计算它对应的动画完成度。这个描述可能有点抽象,这里通过一个例子来说明,例如现在有一个View控件,设置的动画持续时长是1000秒,当进行到200秒的时候,时间也就走动了20%,而此时它的动画完成度也是20%,就是这个时刻View移动到的该位置用插值器去描述。至于该View是按哪种速度模型(匀速、先加速后减速等)来进行变化,那就要看设置的是哪种插值器。所以在Android中插值器也有多种,如图4.2所示。
图4.2 插值器及其资源id
例如系统默认设置的插值器是AccelerateDecelerateInterpolator,它的速度模型是在开始阶段先加速,然后在结束阶段再减速,如图4.3所示。
至于其他插值器在这里就不再讲解了,有兴趣的读者可自行搜索查找。
ViewPropertyAnimator一样可以设置插值器,而且直接调用setInterpolator()方法就可以实现:
图4.3 AccelerateDecelerateInterpolator的速度模型图
ViewPropertyAnimator可以设置监听器,监听View的各种事件,例如动画开始和动画结束等事件从而做出回调处理:
总体来说,ViewPropertyAnimator非常好用,但是它有自己的局限性,那就是只能使用它给定的属性,所以当要使用自定义View新设置的属性或者Android本身自带的View的一些属性时,就没法使用ViewPropertyAnimator来实现。要解决这个问题,就要使用另一个属性动画的工具类,就是ObjectAnimator。
(2)ObjectAnimator
相对于ViewPropertyAnimator,ObjectAnimator可以自己定制属性,也就是想要操作View的属性,就要在创建ObjectAnimator对象时确立下来。最后主动调用它的开始方法让它播放动画效果。
下面同样是用上一个例子来进行讲解,实现View控件向右边平滑移动的动画效果:
ObjectAnimator的构造方法有很多:
●ObjectAnimator.ofFloat();
●ObjectAnimator.ofInt();
●ObjectAnimator.ofObject();
●ObjectAnimator.ofArgb();
●ObjectAnimator.ofMultiFloat();
●ObjectAnimator.ofMultiInt();
●ObjectAnimator.ofPropertyValuesHolder()。
要操作的属性属于什么类型,就用什么类型的构造方法来创建ObjectAnimator对象,它们的用法都是一样的,第一个参数是要操作的目标View控件,第二个参数是该View的要操作的相应属性,第三个参数则是属性值,也就是属性最终的目标值。第三个参数可以是一个,也可以是两个,甚至是多个数。如果是只有一个数,就是代表目标值;如果是两个数就代表第一个数是起始值,第二个数是目标值;如果是多个数,那就代表第一个数是起始值,最后一个数是目标值,中间的数则是转接点(可以理解为拐点)的值。
构造方法的第二个参数是属性名。它是String类型,ObjectAnimator并不是直接对该属性进行操作,而是去寻找该属性对应的setter方法,然后调用它的setter方法来设置属性和做其他逻辑处理操作。
以上都是针对已经有setter方法的属性来使用,那么如果要对自定义View的属性进行操作呢?
首先在自定义View里添加要操作的属性的setter和getter方法,然后调用ObjectAnimator的ofXXX()方法创建ObjectAnimator对象,最后调用ObjectAnimator对象的start()方法执行动画:
上面的代码创建了一个自定义的View,叫作MyView,对其属性indexProperty进行操作,所以先创建了它的setter方法和getter方法,getter方法是用来获取目标值的。最后就是创建ObjectAnimator对象和调用开始方法执行动画。但是,此时运行代码后View并没有任何反应,因为setter还需要再添加一个方法,就是invalidate()方法,由于在Android中界面的绘制都是被动的,setter方法只是修改它的属性值,并不会自动重绘,界面的内容仍然是使用上一次的属性值来进行绘制。所以要及时在属性值修改后通知界面进行重绘,这时就要使用invalidate()方法通知重绘: