Property Animation动画有两个步聚:
1.计算属性值
2.为目标对象的属性设置属性值,即应用和刷新动画
1、首先了解TypeEvaluator的用法(告诉动画系统如何从初始值过度到结束值)
可能在大多数情况下我们使用属性动画的时候都不会用到TypeEvaluator,但是大家还是应该了解一下它的用法,以防止当我们遇到一些解决不掉的问题时能够想起来还有这样的一种解决方案。
ValueAnimator.ofFloat()方法就是实现了初始值与结束值之间的平滑过度,其实就是系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值
- public class FloatEvaluator implements TypeEvaluator {
- public Object evaluate(float fraction, Object startValue, Object endValue) {
- float startFloat = ((Number) startValue).floatValue();
- return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
- }
- }
可以看到,FloatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法。evaluate()方法当中传入了三个参数,
第一个参数fraction非常重要,这个参数用于表示动画的完成度的,我们应该根据它来计算当前动画的值应该是多少,
第二第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了,用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了。
好的,那FloatEvaluator是系统内置好的功能,并不需要我们自己去编写,但介绍它的实现方法是要为我们后面的功能铺路的。前面我们使用过了ValueAnimator的ofFloat()和ofInt()方法,分别用于对浮点型和整型的数据进行动画操作的,但实际上ValueAnimator中还有一个ofObject()方法,是用于对任意对象进行动画操作的。但是相比于浮点型或整型数据,对象的动画操作明显要更复杂一些,因为系统将完全无法知道如何从初始对象过度到结束对象,因此这个时候我们就需要实现一个自己的TypeEvaluator来告知系统如何进行过度。
- 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,如下所示:
如果想让动画生效,要同时满足两个条件:
1. object必须要提供setXxx方法,如果动画的时候没有传递初始值,那么还要提供getXxx方法,因为系统要去拿xxx属性的初始值(如果这条不满足,程序直接Crash)
2. object的setXxx对属性xxx所做的改变必须能够通过某种方法反映出来,比如会带来ui的改变啥的(如果这条不满足,动画无效果但不会Crash)
从源码可以看出,getWidth的确是获取View的宽度的,而setWidth是TextView和其子类的专属方法,它的作用不是设置View的宽度,而是设置TextView的最大宽度和最小宽度的,这个和TextView的宽度不是一个东西
具体来说,TextView的宽度对应Xml中的android:layout_width属性,而TextView还有一个属性android:width,这个android:width属性就对应了TextView的setWidth方法。
针对上述问题,Google告诉我们有3中解决方法:
1. 给你的对象加上get和set方法,如果你有权限的话
2. 用一个类来包装原始对象,间接为其提供get和set方法
3. 采用ValueAnimator,监听动画过程,自己实现属性的改变
1、给你的对象加上get和set方法,如果你有权限的话
这个的意思很好理解,如果你有权限的话,加上get和set就搞定了,但是很多时候我们没权限去这么做,比如本文开头所提到的问题,你无法给Button加上一个合乎要求的setWidth方法,因为这是Android SDK内部实现的。这个方法最简单,但是往往是不可行的,这里就不对其进行更多分析了。
2、用一个类来包装原始对象,间接为其提供get和set方法
这是一个很有用的解决方法,是我最喜欢用的,因为用起来很方便,也很好理解,下面将通过一个具体的例子来介绍它
- private void performAnimate() {
- ViewWrapper wrapper = new ViewWrapper(mButton);
- ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
- }
- @Override
- public void onClick(View v) {
- if (v == mButton) {
- performAnimate();
- }
- }
- private static class ViewWrapper {
- private View mTarget;
- public ViewWrapper(View target) {
- mTarget = target;
- }
- public int getWidth() {
- return mTarget.getLayoutParams().width;
- }
- public void setWidth(int width) {
- mTarget.getLayoutParams().width = width;
- mTarget.requestLayout();
- }
- }
3、采用ValueAnimator,监听动画过程,自己实现属性的改变
首先说说啥是ValueAnimator,ValueAnimator本身不作用于任何对象,也就是说直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。
- private void performAnimate(final View target, final int start, final int end) {
- ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
- valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
- //持有一个IntEvaluator对象,方便下面估值的时候使用
- private IntEvaluator mEvaluator = new IntEvaluator();
- 每一帧都会回调这个方法
- @Override
- public void onAnimationUpdate(ValueAnimator animator) {
- //获得当前动画的进度值,整型,1-100之间
- int currentValue = (Integer)animator.getAnimatedValue();
- Log.d(TAG, "current value: " + currentValue);
- //计算当前进度占整个动画过程的比例,浮点型,0-1之间
- float fraction = currentValue / 100f;
- //这里我偷懒了,不过有现成的干吗不用呢
- //直接调用整型估值器通过比例计算出宽度,然后再设给Button
- target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
- target.requestLayout();
- }
- });
- valueAnimator.setDuration(5000).start();
- }
- @Override
- public void onClick(View v) {
- if (v == mButton) {
- performAnimate(mButton, mButton.getWidth(), 500);
- }
- }
上述代码的动画效果图和采用ViewWrapper是一样的,请参看上图。关于这个ValueAnimator我要再说一下,拿上例来说,它会在5000ms内将一个数从1变到100,然后动画的每一帧会回调onAnimationUpdate方法,在这个方法里,我们可以获取当前的值(1-100),根据当前值所占的比例(当前值/100),我们可以计算出Button现在的宽度应该是多少,比如时间过了一半,当前值是50,比例为0.5,假设Button的起始宽度是100px,最终宽度是500px,那么Button增加的宽度也应该占总增加宽度的一半,总增加宽度是500-100=400,所以这个时候Button应该增加宽度400*0.5=200,那么当前Button的宽度应该为初始宽度+ 增加宽度(100+200=300)。上述计算过程很简单,其实它就是整型估值器IntEvaluator的内部实现,所有我们不用自己写了,直接用吧。
- Duration:动画的持续时间,单位ms
- TimeInterpolation:属性值的计算方式,如先快后慢,是一个接口。用来设置插值器
- TypeEvaluator(估值器):根据属性的开始、结束值与TimeInterpolation计计算出“当前”时间的属性值,这个值可以在AnimationUpdate中得到
- Repeat Country and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以此动画一直重复,或播放完时再反向播放
- Animation sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同开始偏移
- Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响