Android属性动画(二)—— 插值器和估值器

版权声明:本文为博主原创文章,转载请注明出处: https://blog.csdn.net/qq_38182125/article/details/89631460

一、概述

本篇文章续接Android属性动画(一),介绍属性动画中非常重要的两个部分:插值器(Interpolator)和估值器(TypeEvaluator)。它们在属性动画中分别用于动画随时间流逝的变化规律和定义从初始值过渡到结束值的计算规则,本文结构如下:

  • 插值器(Interpolator)的介绍和使用
  • 估值器(TypeEvaluator)的介绍和使用

二、插值器Interpolator

插值器(Interpolator)用于定义动画随时间流逝的变化规律。这句话说起来比较抽象,但其实在我们实际使用属性动画的时候,我们能明显感觉到插值器的作用。

例如默认采用的加减速插值器,它会在动画开始执行逐渐加速,然后又逐渐减速直至动画结束;而线性插值器则是按着一个均匀的速度执行完整个动画的。它们随着动画执行比例的变化规律如下图所示:
在这里插入图片描述 在这里插入图片描述
图中线上某点所在的斜率即为在某时刻的速率。从图上也可以明显看到加减速插值器先逐渐加速、再逐渐减速,而线性插值器一直保持恒定的速率执行完成。在知道了插值器的作用之后,接下来我们就来看Android中有哪些内置的插值器。

1. 系统内置的插值器

Android系统中共内置了9种插值器,如下表所示:

插值器名称 作用
AccelerateDecelerateInterpolator 加减速插值器,动画先加速,后减速
LinearInterpolator 线性插值器,动画匀速运行
AccelerateInterpolator 加速插值器,动画加速运行至结束
DecelerateInterpolator 减速插值器,动画减速运行至结束
OvershootInterpolator 快速完成动画,超出终点一小部分后再回到终点
AnticipateInterpolator 先后退一小步再加速前进至结束
AnticipateOvershootInterpolator 先后退一小步再加速前进,超出终点一小部分后再回到终点
BounceInterpolator 弹性插值器,在动画结束之前会有一个弹性动画的效果
CycleInterpolator 周期运动

文字说明比较抽象,笔者下面通过图像的形式来展示这些插值器随时间流逝的变化效果:

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
接下来我们就来看到插值器的相关接口以及如何自定义一个插值器。

2. TimeInterpolator接口

自定义插值器我们需要实现 Interpolator / TimeInterpolator 接口并实现接口方法getInterpolation。属性动画实现的是TimeInterpolator接口,而补间动画实现的则是Interceptor接口。事实上Interpolator接口继承了TimeInterceptor接口,这样做是基于兼容性的考虑,使得过去所有应用于补间动画的插值器都可以直接应用在属性动画上。为了我们的自定义插值器具有更好的兼容性,推荐实现Interpolator接口。

接下来看到TimeInterpolator接口的定义:

/**
 * A time interpolator defines the rate of change of an animation. This allows animations
 * to have non-linear motion, such as acceleration and deceleration.
 */
public interface TimeInterpolator {

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);
}

可以看到这个接口只有一个接口方法getInterpolation方法需要实现,它是用于定义动画随时间流逝的变化规律的,它的参数input表示的是当前动画执行比例,如0.5表示动画现在执行了50%。返回值表示动画的完成度,在属性动画中称之为fraction

我们通过系统内置的几个插值器类来看看如何使用这个方法,先看到LinearInterpolator

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

   ......
}

可以看到LinearInterpolator继承自BaseInterpolator,这是一个抽象类,它实现了Interpolator接口。所以LinearInterpolator相当于间接实现了Interpolator接口,接下来看到getInterpolation的实现:

public float getInterpolation(float input) {
    return input;
}

可以看到,它就是简单地将输入参数input返回了。虽然简单的不可思议,但是也很容易理解,结合前面的图,LinearInterpolator是随着时间流逝匀速变化的,所以它的变化是线性的,我们只需要直接返回input即可。接下来看一个稍微复杂些的AccelerateInterpolator,也就是加速度插值器:

public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    ......

    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

    ......
}

在默认情况下,mFactor的值为1.0f,也就是说它的返回值是input*input,结合一定的数学知识和上面的相关图像我们很容易得知,在0~1这个区间上斜率是逐渐增加的,符合加速度插值器的特点。在知道了插值器是如何实现了之后,接下来我们来尝试自定义一个插值器。

3. 自定义插值器

接下来我们自定义一个减速加速插值器,称为DecelerateAccelerateInterpolator,它的变化图像如下所示:
在这里插入图片描述
它的实现代码如下,由于主要是数学知识,这里就不再对代码进行赘述:

public class DecelerateAccelerateInterpolator implements Interpolator {

    @Override
    public float getInterpolation(float input) {
        input -= 0.5;
        return (float) Math.pow(input, 3) * 4.0f + 0.5f;
    }
}

应用到实际的动画中,代码如下:

ObjectAnimator anim = ObjectAnimator.ofFloat(mButton, "rotation", 0.0f, 360.0f);
anim.setDuration(5000);
anim.setInterpolator(new DecelerateAccelerateInterpolator());
anim.start();

效果如下所示:
在这里插入图片描述
可以明显看到先减速后加速的效果,以上就是关于自定义插值器的简单示例了。接下来我们来介绍属性动画中另一个非常重要的部分:估值器。


三、估值器(TypeEvaluator)

估值器(TypeEvaluator)的作用是定义从初始值过渡到结束值的计算规则。当我们采用如下的方式创建属性动画时:

ValueAnimator anim = ValueAnimator.ofFloat(0.0f, 360.0f);

ValueAnimator.ofFloat方法其实就实现了初始值到结束值过渡的规则定义,我们在使用过程中没有感受到估值器 TypeEvaluator 的存在是因为这个方法内置了FloatEvaluator来对浮点值从初始值到结束值进行了定义,在看FloatEvaluator的源码之前,我们先来查看每个估值器都要实现的接口:TypeEvaluator。

1. TypeEvaluator接口

TypeEvaluator接口的定义如下所示:

public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}

可以看到这个接口接收一个泛型参数T,用于其唯一的方法evaluate的参数类型以及返回值类型。该方法的参数含义以及返回值的含义如下表所示:

名称 含义
fraction 表示动画的完成度,它在属性动画中的取值其实就是插值器 getInterpolation 方法的返回值
startValue 表示动画的起始值
endValue 表示动画的结束值
return 返回值即为当前完成度下的所计算出来的值

接下来我们就来看系统内置的实现类 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所做的事就是简单地将结束值减去初始值乘以fraction后再加上初始值所得的结果进行返回,这跟我们平常计算比例值的方式是一样的。而IntEvaluator的实现也是差不多的,这两个类已经能够涵盖大部分动画所遇到的情况了,但是当我们遇到一些更加复杂的操作时,这两个类可能并不够用,接下来我们就来看如何自定义一个估值器。

2. 自定义估值器

在这里我会举两个例子来介绍自定义估值器的使用,首先我们先来看到第一个:自定义字符变化的估值器,将字符按照字母表的顺序进行过渡。这个需求比较简单,代码如下所示:

public class CharEvaluator implements TypeEvaluator<Character> {
    @Override
    public Character evaluate(float fraction, Character startValue, Character endValue) {
        return (char) (startValue + (endValue - startValue) * fraction);
    }
}

然后在Activity中,我们需要借助ValueAnimator.ofObject方法来使用自定义的估值器:

ValueAnimator anim = ValueAnimator.ofObject(new CharEvaluator(), 'A', 'Z');
anim.setDuration(500);
anim.setInterpolator(new LinearInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Log.d(TAG, "current character is " + animation.getAnimatedValue());
    }
});
anim.start();

为了能够观察到值的变化,我们为其添加监听,通过Log打印结果,结果如下所示:

D/MainActivity: current character is A
D/MainActivity: current character is A
D/MainActivity: current character is B
D/MainActivity: current character is C

D/MainActivity: current character is Y
D/MainActivity: current character is Z

可以看到成功地按照我们定义的方式从字符A过渡到了字符Z。当然这个自定义的估值器比较粗糙,仅做实验用,实际使用需要做更多改进!接下来我们来看一个在自定义View中应用的估值器,这个例子来自郭神的博客。

首先我们先自定义一个类Point表示坐标:

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 void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }
}

接着我们为Point定义一个估值器PointEvaluator

public class PointEvaluator implements TypeEvaluator<Point> {
    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        float x = startValue.getX() + (endValue.getX() - startValue.getX()) * fraction;
        float y = startValue.getY() + (endValue.getY() - startValue.getY()) * fraction;
        return new Point(x, y);
    }
}

接着我们自定义一个View,如下所示:

public class CircleView extends View {

    private static final float RADIUS = 100.0f;

    private Point point;

    private Paint mPaint;

    public CircleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
    }

    @Override
    protected void onDraw(Canvas canvas){
        if (point == null){
            point = new Point(RADIUS, RADIUS);
            drawCircle(canvas);
        } else {
            drawCircle(canvas);
        }
        invalidate();
    }

    private void drawCircle(Canvas canvas) {
        float x = point.getX();
        float y = point.getY();
        canvas.drawCircle(x, y, RADIUS, mPaint);
    }

    public Point getPoint(){
        return point;
    }

    public void setPoint(Point point){
        this.point = point;
    }
}

在这里需要特别说明2点:

  • 本例子中我们需要修改的是Point的属性值,所以在自定义的类中我们必须要提供相应的setPointgetPoint方法,因为属性动画是通过反射的原理来修改相应的属性值的。
  • 在重写onDraw方法的时候要特别注意调用invalidate方法。

接下来在Activity中,代码如下所示:

ObjectAnimator anim = ObjectAnimator.ofObject(circleView, "point",
        new PointEvaluator(),
        new Point(100.0f, 100.0f), new Point(500.0f, 500.0f));
anim.setDuration(2000);
anim.start();

对于自定义的估值器,调用的同样是ofObject方法,我们这里将圆从(100, 100)的位置移动到(500, 500)的位置。运行代码,效果如下所示:
在这里插入图片描述
可以看到通过自定义Point属性的估值器,我们成功实现了圆的移动。以上就是关于估值器的所有内容了。至此本文所有的内容均介绍完毕。


参考

Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法


希望这篇文章对你有所帮助~

猜你喜欢

转载自blog.csdn.net/qq_38182125/article/details/89631460