Android属性动画--TypeEvaluator

来源:互联网 发布:推荐知乎有趣的话题 编辑:程序博客网 时间:2024/05/22 08:27

1、功能介绍

可能在大多数情况下我们使用属性动画的时候都不会用到TypeEvaluator,因为属性动画已经比补间动画强大了许多,基本使用已经不成问题。但如果我们遇到了难以解决的问题的时候,使用 TypeEvaluator 可能会有意想不到的效果。

TypeEvaluator 的意思是估值器,所以它的作用就是告诉动画系统如何从初始值过度到结束值。我们学习 ValueAnimator 的时候学到的ValueAnimator.ofFloat()方法就是实现了初始值与结束值之间的平滑过渡,那么这个平滑过渡是怎么做到的呢?

其实就是系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值,TypeEvaluator 是个接口,只有一个evaluate()方法。它用来返回你要进行动画的那个属性在当前时间点所需要的属性值。我们来看看 FloatEvaluator 的代码实现:

public class FloatEvaluator implements TypeEvaluator<Number> {    public Float evaluate(float fraction, Number startValue, Number endValue) {        float startFloat = startValue.floatValue();        return startFloat + fraction * (endValue.floatValue() - startFloat);    }}

可以看到,FloatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法。evaluate()方法当中传入了三个参数,第一个参数fraction非常重要,这个参数用于表示动画的完成度的,可以简单地理解成animator当前播放的进度条的位置。我们应该根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。

那么上述代码的逻辑就比较清晰了,用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了。

2、Interpolator 与 Evaluator

这里我再提下 Interpolator 与 Evaluator 的关系。要知道Interpolator 的意思是插值器,而 Evaluator 则是估值器:

  • Interpolator:用来定义animator变化的速率。它让基础的动画效果(渐变、拉伸、平移、旋转)有加速、减速、重复等效果。在源代码中,其实interpolation的作用就是根据某一个时间点来计算它的播放时间分数(fraction)
  • evaluator:evaluator 全部继承与 TypeEvaluator接口,它只有一个 evaluate()方法,用来返回你要进行动画的那个属性在当前时间点所需要的属性值。

我们可以把动画的过程想象成是一部电影的播放,电影的播放中有进度条,Interpolator就是用来控制电影播放频率,也就是快进快退要多少倍速。然后Evaluator根据Interpolator提供的值计算当前播放电影中的哪一个画面,也就是进度条要处于什么位置。

因为 fraction 是由 Interpolator 计算出来的,所以它们两个是密不可分的。

3、自定义TypeEvaluator

我们使用过了ValueAnimator的ofFloat()和ofInt()方法,分别用于对浮点型和整型的数据进行动画操作的,但实际上ValueAnimator中还有一个ofObject()方法,是用于对任意对象进行动画操作的。但是相比于浮点型或整型数据,对象的动画操作明显要更复杂一些,因为系统将完全无法知道如何从初始对象过度到结束对象,因此这个时候我们就需要实现一个自己的TypeEvaluator来告知系统如何进行过度。

这里我的 View 是一个小球,我们来实现给它做抛物线运动,首先就为它写个实体类。

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;      }   }

这里 x,y用于记录坐标的位置,并提供了构造方法来设置坐标,以及get方法来获取坐标。

public class PointEvaluator implements TypeEvaluator {    @Override    public Object evaluate(float fraction, Object startValue, Object endValue) {        float x = 200 * fraction * 3 + 32;        float y =0.5f * 200 * (fraction * 3) * (fraction * 3) + 32;        Point point = new Point(x, y);        return point;    }}

这就是我们自定义的 TypeEvaluator,因为x,y这两个坐标的单位是像素,而我的布局中设置了padding,值为16dp,而我的设备是320dpi的,所以换算成像素是32像素点,所以x,y的初始值为32。

这里要做的是抛物线,我定义的 x 方向上的速度为 200px/s,因为在后面会看到我把 Duration 设置为3秒,所以要对 fraction 这个进度值乘3才是正确的变化的时间。x=vt,y=1/2 * gt ^2,这个重力加速度设置为200px/s^2,这样就得到了变化的小球。

imageView.setOnClickListener(new View.OnClickListener() {   @Override   public void onClick(final View v) {       ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(), new Point(0,0));       animator.setInterpolator(new LinearInterpolator());       animator.setDuration(3000);       animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {          @Override          public void onAnimationUpdate(ValueAnimator animation) {              Point point = (Point) animation.getAnimatedValue();              v.setX(point.getX());              v.setY(point.getY());           }       });       animator.start();    }});

因为我们在自定义 TypeEvaluator 中没有用到startValue 和 endValue,所以在 ofObject() 中传入的 Point 对象设置的值并没有作用。

这里同样可以将 Evaluator 和 ObjectValues 分开写:

ValueAnimator animator = new ValueAnimator();animator.setObjectValues(new Point(0, 0));animator.setEvaluator(new PointEvaluator());

这样效果是一样的。

自定义 TypeEvaluator 的大致流程就是这样,如果你对 ValueAnimator 产生的值不满意的话,自定义 TypeEvaluator 不失为一个好办法。

4、动态改变View颜色

我们在介绍 Property Animation时,免不了拿它与 Tweened Animations 去比较,补间动画只能实现移动、缩放、旋转和淡入淡出这四种动画操作,基本上没有任何扩展性。所以我们想要实现对View的颜色进行动态改变,补间动画是没有办法做到的。

这次我们的自定义TypeEvaluator是改变字符串color的值,这里我们可以将color属性设置为字符串类型,使用#RRGGBB这种格式来表示颜色值,代码如下:

public class ColorEvaluator implements TypeEvaluator {    private int mCurrentRed = -1;    private int mCurrentGreen = -1;    private int mCurrentBlue = -1;    @Override    public Object evaluate(float fraction, Object startValue, Object endValue) {        String startColor = (String) startValue;        String endColor = (String) endValue;        int startRed = Integer.parseInt(startColor.substring(1, 3), 16);        int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);        int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);        int endRed = Integer.parseInt(endColor.substring(1, 3), 16);        int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);        int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);        // 初始化颜色的值        if (mCurrentRed == -1) {            mCurrentRed = startRed;        }        if (mCurrentGreen == -1) {            mCurrentGreen = startGreen;        }        if (mCurrentBlue == -1) {            mCurrentBlue = startBlue;        }        // 计算初始颜色和结束颜色之间的差值        int redDiff = Math.abs(startRed - endRed);        int greenDiff = Math.abs(startGreen - endGreen);        int blueDiff = Math.abs(startBlue - endBlue);        int colorDiff = redDiff + greenDiff + blueDiff;        if (mCurrentRed != endRed) {            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,                    fraction);        } else if (mCurrentGreen != endGreen) {            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,                    redDiff, fraction);        } else if (mCurrentBlue != endBlue) {            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,                    redDiff + greenDiff, fraction);        }        // 将计算出的当前颜色的值组装返回        String currentColor = "#" + getHexString(mCurrentRed)                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);        return currentColor;    }    /**     * 根据fraction值来计算当前的颜色。     */    private int getCurrentColor(int startColor, int endColor, int colorDiff,                                int offset, float fraction) {        int currentColor;        if (startColor > endColor) {            currentColor = (int) (startColor - (fraction * colorDiff - offset));            if (currentColor < endColor) {                currentColor = endColor;            }        } else {            currentColor = (int) (startColor + (fraction * colorDiff - offset));            if (currentColor > endColor) {                currentColor = endColor;            }        }        return currentColor;    }    /**     * 将10进制颜色值转换成16进制。     */    private String getHexString(int value) {        String hexString = Integer.toHexString(value);        if (hexString.length() == 1) {            hexString = "0" + hexString;        }        return hexString;    }}

首先在 evaluate() 方法当中获取到颜色的初始值和结束值,并通过字符串截取的方式将颜色分为RGB三个部分,并将RGB的值转换成十进制数字,那么每个颜色的取值范围就是0~255。

接下来计算一下初始颜色值到结束颜色值之间的差值,这个差值很重要,决定着颜色变化的快慢。在相同的时间内,如果初始颜色值和结束颜色值很相近,那么颜色变化就会比较缓慢,而如果颜色值相差很大,比如说从黑到白,那么就要经历255*3这个幅度的颜色过度,变化就会非常快。

控制颜色变化的速度是通过getCurrentColor()这个方法来实现的,这个方法会根据当前的fraction值来计算目前应该过渡到什么颜色,并且这里会根据初始和结束的颜色差值来控制变化速度,最终将计算出的颜色进行返回。

最后,由于我们计算出的颜色是十进制数字,这里还需要调用一下getHexString()方法把它们转换成十六进制字符串,再将RGB颜色拼装起来之后作为最终的结果返回。

完成了自定义的TypeEvaluator,接下来当然是调用啦,这里我们将它和上面实现的 PointEvaluator 结合起来播放。

imageView.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(final View v) {        ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(), new Point(0,0));        ValueAnimator animator1 = ValueAnimator.ofObject(new ColorEvaluator(), "#0000FF", "#FF0000");        AnimatorSet animatorSet = new AnimatorSet();        animatorSet.setDuration(3000);        animatorSet.play(animator).with(animator1);        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                Point point = (Point) animation.getAnimatedValue();                v.setX(point.getX());                v.setY(point.getY());            }        });        animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                String str = (String) animation.getAnimatedValue();                GradientDrawable drawable = (GradientDrawable) imageView.getBackground();                drawable.setColor(Color.parseColor(str));            }        });        animatorSet.start();    }});

这里要注意的是,我给 ImageView 的背景设置的是ShapeDrawable,所以可以用 GradientDrawable去修改填充的颜色,这里用Color类的parseColor() 方法将带“#”的字符串转换成 int,所以在 ColorEvaluator 里生成字符串的时候“#”是不能少的。

这里因为要对View的color进行改变,所以我们也可以使用 ObjectAnimator 来实现这一动画。

了解 ObjectAnimator 的朋友应该都知道,ObjectAnimator内部的工作机制是通过寻找特定属性的get和set方法,然后通过方法不断地对值进行改变,从而实现动画效果。

因此我们就需要在我们的View中定义一个color属性,并提供它的get和set方法。这里就只提一下,就不做介绍啦。

到此,TypeEvaluator的介绍就结束啦。

结束语:本文仅用来学习记录,参考查阅。

3 0
原创粉丝点击