ValueAnimator是属性动画中的一个重要的类,其内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。ObjectAnimator其实就是ValueAnimator的一个子类,之所以先了解ObjectAnimator<<属性动画之ObjectAnimator>>,是因为ObjectAnimator用的比较多,功能完善,比较直观,容易理解,而ValueAnimator一般在自定义一些复杂的动画时会使用。
ValueAnimator的使用与ObjectAnimator类似,也提供了一些ofXXX方法,先看一个简单的例子,
private void animation() {
ValueAnimator animator = ValueAnimator.ofFloat(0f, 350f, 0f );
animator.setDuration(2000);
animator.setTarget(head);
animator.setRepeatCount(Animation.INFINITE);
animator.start();
}
执行这段代码后,发现没有什么变化,这一点也不奇怪,因为我们没有设定操作对象的属性,系统肯定不知道该如何处理了。那如何设置属性呢?但我们发现ValueAnimator的ofXXX方法中没有设置属性的参数,也没用setXXX方法,那就先放一边。
在讲解ObjectAnimator这块内容时,有说过AnimatorUpdateListener这个接口,可以读取到动画的每个更新值,当前的例子在界面上没有任何现象,那这个更新值是否在变化呢?尝试加上这个接口,打印动画的值,
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.d("dingfeng", "animation value:"+(float)animation.getAnimatedValue());
}
});
打印log,06-01 11:15:59.819 20681-20681/? D/dingfeng: animation value:260.29874
06-01 11:15:59.839 20681-20681/? D/dingfeng: animation value:269.3637
06-01 11:15:59.859 20681-20681/? D/dingfeng: animation value:278.48615
06-01 11:15:59.869 20681-20681/? D/dingfeng: animation value:287.6596
06-01 11:15:59.889 20681-20681/? D/dingfeng: animation value:296.8775
06-01 11:15:59.909 20681-20681/? D/dingfeng: animation value:305.58807
06-01 11:15:59.929 20681-20681/? D/dingfeng: animation value:314.87332
06-01 11:15:59.939 20681-20681/? D/dingfeng: animation value:324.1839
06-01 11:15:59.959 20681-20681/? D/dingfeng: animation value:333.51276
06-01 11:15:59.979 20681-20681/? D/dingfeng: animation value:342.85336
06-01 11:15:59.989 20681-20681/? D/dingfeng: animation value:347.80093
06-01 11:16:00.009 20681-20681/? D/dingfeng: animation value:338.45682
我们可以看到动画的值是更新的,那就可以可以根据这个值来自己设置操作的对象的属性,比如水平移动,animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.d("dingfeng", "animation value:"+(float)animation.getAnimatedValue());
head.setTranslationX((float)animation.getAnimatedValue());
}
});
这样也达到了水平移动的效果,看似也比较简单。
ValueAnimator也是可以设置任意属性的,使用ofObject方法,在此之前我们需要先了解TypeEvaluator的使用。
TypeEvaluator
ofObject()方法和ofInt(),ofFloat()不同,需要自己实现TypeEvaluator,而其他两个方法系统内部都帮忙实现了其方法,分别是IntEvaluator和FloatEvaluator类型,那就看一下FloatEvaluator时如何实现的。
public class FloatEvaluator implements TypeEvaluator<Number> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type <code>float</code> or
* <code>Float</code>
* @param endValue The end value; should be of type <code>float</code> or <code>Float</code>
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
可以看到,FloatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法。evaluate()方法当中传入了三个参数,第一个参数fraction非常重要,这个参数用于表示动画的完成度的(fraction的值在0~1之间变化),我们应该根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了,用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了。
现在看这样一个例子,让左上角的图片沿着屏幕四周滑动,最后回到左上角。
考虑动画的整个过程,左上角-->左下角-->右下角-->右上角-->左上角,一共四个过程,每两个角直接就是一个过程,那我们应该要知道四个角的坐标(PonitF)
// 先设定屏幕四个角的坐标,注意空间的宽高和状态栏的高度
PointF start = new PointF(0, 0);
PointF point1 = new PointF(0, screenHeight - imgHeight - statusBarHeight);
PointF point2 = new PointF(screenWidth - imgWidth, screenHeight - imgHeight - statusBarHeight);
PointF point3 = new PointF(screenWidth - imgWidth, 0);
PointF end = new PointF(0, 0);
然后就要计算滑动过程中的坐标值,这个时候就要自定义一个TypeEvaluator,实现evaluate,上面已经对该方法进行了解释,那我们这里就很容易处理了。比如,
x的坐标值 = 初始值 + (结束值 - 初始值)* 完成度,然后同样的方式计算得出y的坐标值,最后生成新坐标,
ValueAnimator animator2 = ValueAnimator.ofObject(new TypeEvaluator<PointF>() {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
// 计算滑动过程中的水平和垂直距离,生成新的坐标
float x = startValue.x + fraction * (endValue.x - startValue.x);
float y = startValue.y + fraction * (endValue.y - startValue.y);
PointF point = new PointF(x, y);
return point;
}
}, start, point1, point2, point3, end);
实现AnimatorUpdateListener接口,读取到动画的每个更新值,将动画值转化为坐标,设置操作对象的x和y值,这样位置就更新了。
animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 将动画值转化为为PointF对象,然后更新空间的位置
PointF current = (PointF) animation.getAnimatedValue();
head.setX(current.x);
head.setY(current.y);
}
});
当然,我们这里实现的TypeEvaluator也还是比较简单的,真正复杂的动画效果要编写一个TypeEvaluator还是要花一些精力的。