Android动画系列之插值器(Interpolator)和估值器(TypeEvaluator)详解

Android动画系列之插值器(Interpolator)和估值器(TypeEvaluator)详解

1、插值器(Interpolator)

Interpolator定义了一个动画中的特定值作为时间的函数的计算(根据时间流逝的百分比计算出当前属性值改变的百分比)。例如,您可以指定在整个动画过程中线性的动画,使动画在整个时间内均匀地移动,或者你可以指定要使用的非线性的动画,加速开始,减速结束动画

1.1、插值器分类
Interpolatorxml资源id作用
AccelerateDecelerateInterpolator@android:anim/accelerate_decelerate_interpolator变化速度开始和结束缓慢,但在中间加速。
AccelerateInterpolator@android:anim/accelerate_interpolator变化速度开始缓慢,然后加速
AnticipateInterpolator@android:anim/anticipate_interpolator其变化从后向开始,然后向前抛出
AnticipateOvershootInterpolator@android:anim/anticipate_overshoot_interpolator其变化从后向开始,向前抛出并超调目标值,然后返回到最后的值
BounceInterpolator@android:anim/bounce_interpolator其变化在末端反弹
CycleInterpolator@android:anim/cycle_interpolator动画在一定数目的循环中重复
DecelerateInterpolator@android:anim/decelerate_interpolator变化速度迅速开始,然后减速
LinearInterpolator@android:anim/linear_interpolator变化率为常数
OvershootInterpolator@android:anim/overshoot_interpolator其变化会向前抛出,并超过最后一个值,然后返回
TimeInterpolator一个接口,可以自定义插值器

系统默认的插值器是AccelerateDecelerateInterpolator

1.2、插值器的使用

当在XML文件设置插值器时,只需传入对应的插值器资源ID即可

<set android:interpolator="@android:anim/accelerate_interpolator">
    ...
</set>

当在Java代码设置插值器时,只需创建对应的插值器对象即可

Animation.setInterpolator();

Animator.setInterpolator();
1.3、自定义插值器
1.3.1、XML自定义

步骤1、在res/anim/目录下创建filename.xml文件;

步骤2、修改自定义的插值器xml文件

<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:factor="2">

</accelerateInterpolator>

通过xml定义的插值器,用于xml定义的动画

有些插值器的一些属性,但是有些插值器却不具备修改属性:

<accelerateDecelerateInterpolator>

无可自定义的attribute。

<accelerateInterpolator>

android:factor 浮点值,加速速率(默认值为1)。

<anticipateInterploator>

android:tension 浮点值,起始点后拉的张力数(默认值为2)。

<anticipateOvershootInterpolator>

android:tension 浮点值,起始点后拉的张力数(默认值为2)。
android:extraTension 浮点值,拉力的倍数(默认值为1.5)。

<bounceInterpolator>

无可自定义的attribute。

<cycleInterplolator>

android:cycles 整形,循环的个数(默认为1)。

<decelerateInterpolator>

android:factor 浮点值,减速的速率(默认为1)。

<linearInterpolator>

无可自定义的attribute。

<overshootInterpolator>

android:tension 浮点值,超出终点后的张力(默认为2)

这些属性都可以通过方法来设置。

1.3.2、自定义Interpolator类

来看一下AccelerateInterpolator的源码:

@HasNativeInterpolator
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    private final float mFactor;
    private final double mDoubleFactor;

    public AccelerateInterpolator() {
        mFactor = 1.0f;
        mDoubleFactor = 2.0;
    }

    /**
     * Constructor
     *
     * @param factor Degree to which the animation should be eased. Seting
     *        factor to 1.0f produces a y=x^2 parabola. Increasing factor above
     *        1.0f  exaggerates the ease-in effect (i.e., it starts even
     *        slower and ends evens faster)
     */
    public AccelerateInterpolator(float factor) {
        mFactor = factor;
        mDoubleFactor = 2 * mFactor;
    }

    public AccelerateInterpolator(Context context, AttributeSet attrs) {
        this(context.getResources(), context.getTheme(), attrs);
    }

    /** @hide */
    public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
        TypedArray a;
        if (theme != null) {
            a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0);
        } else {
            a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator);
        }

        mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
        mDoubleFactor = 2 * mFactor;
        setChangingConfiguration(a.getChangingConfigurations());
        a.recycle();
    }

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

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
    }
}

插值器的本质就是根据动画的进度(0%-100%)计算出当前属性值改变的百分比。

其中实现的地方就在getInterpolation(float input)方法中。

自定义插值器需要实现 Interpolator / TimeInterpolator接口和复写getInterpolation();

补间动画 实现 Interpolator接口;属性动画实现TimeInterpolator接口。

TimeInterpolator接口是属性动画中新增的,用于兼容Interpolator接口,这使得所有过去的Interpolator实现类都可以直接在属性动画使用。

下面自定义一个先减速后加速的插值器:

public class DecelerateAccelerateInterpolator implements TimeInterpolator{

    public DecelerateAccelerateInterpolator() {
    }

    @Override
    public float getInterpolation(float input) {
        float result;
        if (input <= 0.5) {
            result = (float) (Math.sin(Math.PI * input)) / 2;
            // 使用正弦函数来实现先减速后加速的功能,逻辑如下:
            // 因为正弦函数初始弧度变化值非常大,刚好和余弦函数是相反的
            // 随着弧度的增加,正弦函数的变化值也会逐渐变小,这样也就实现了减速的效果。
            // 当弧度大于π/2之后,整个过程相反了过来,现在正弦函数的弧度变化值非常小,渐渐随着弧度继续增加,变化值越来越大,弧度到π时结束,这样从0过度到π,也就实现了先减速后加速的效果
        } else {
            result = (float) (2 - Math.sin(Math.PI * input)) / 2;
        }
        return result;
    }
}

2、估值器(TypeEvaluator)

估值器是用来根据当前属性改变的百分比来计算改变后的属性值。

2.1、估值器分类

系统默认的有IntEvaluator,FloatEvaluator,ArgbEvaluator。

属性动画中的ValueAnimator.ofInt() & ValueAnimator.ofFloat()& ValueAnimator.ofArgb()都具备系统内置的估值器,即FloatEvaluator & IntEvaluator & ArgbEvaluator.

但是ValueAnimator.ofObject()没有默认实现估值器,需要我们自己自定义估值器。

2.2、自定义估值器

自定义估值器需要实现 TypeEvaluator接口 & 复写evaluate()方法。

先来分析一下FloatEvaluator源码:

public class FloatEvaluator implements TypeEvaluator<Number> {

    /**
         * @param fraction   表示动画完成度(根据它来计算当前动画的值)
         * @param startValue 动画的初始值
         * @param endValue   动画的结束值
         * @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);
        // 初始值 过渡 到结束值 的算法是:
        // 1. 用结束值减去初始值,算出它们之间的差值
        // 2. 用上述差值乘以fraction系数
        // 3. 再加上初始值,就得到当前动画的值
    }
}

我们以一个圆从一个点移动到另外一个点为例自定义一个TypeEvaluator:

步骤1、定义一个圆的坐标点的实体类
public class Point {

    private float x;//x轴坐标值
    private float y;//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;
    }
}
步骤2、实现 TypeEvaluator接口 & 复写evaluate()方法
public class PointEvaluator implements TypeEvaluator<Point> {

    public PointEvaluator() {
    }

    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {

        // 根据fraction来计算当前动画的x和y的值
        float x = startValue.getX() + fraction * (endValue.getX() - startValue.getX());
        float y = startValue.getY() + fraction * (endValue.getY() - startValue.getY());

        // 将计算后的坐标封装到一个新的Point对象中并返回
        Point point = new Point(x, y);
        return point;
    }
}
步骤3、自定义一个view,定义属性动画
public class TypeEvaluatorView extends View {

    public static final float RADIUS = 70f;// 圆的半径 = 70
    private Point currentPoint;// 当前点坐标
    private Paint mPaint;// 绘图画笔

    public TypeEvaluatorView(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 (currentPoint == null) {
            currentPoint = new Point(RADIUS, RADIUS);
            // 创建一个点对象(坐标是(70,70))

            // 在该点画一个圆:圆心 = (70,70),半径 = 70
            float x = currentPoint.getX();
            float y = currentPoint.getY();
            canvas.drawCircle(x, y, RADIUS, mPaint);


            // (重点关注)将属性动画作用到View中
            // 步骤1:创建初始动画时的对象点  & 结束动画时的对象点
            Point startPoint = new Point(RADIUS, RADIUS);// 初始点为圆心(70,70)
            Point endPoint = new Point(700, 1000);// 结束点为(700,1000)

            // 步骤2:创建动画对象 & 设置初始值 和 结束值
            ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);

            // 步骤3:设置动画参数
            anim.setDuration(5000);
            // 设置动画时长

// 步骤3:通过 值 的更新监听器,将改变的对象手动赋值给当前对象
// 此处是将 改变后的坐标值对象 赋给 当前的坐标值对象
            // 设置 值的更新监听器
            // 即每当坐标值(Point对象)更新一次,该方法就会被调用一次
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    currentPoint = (Point) animation.getAnimatedValue();
                    // 将每次变化后的坐标值(估值器PointEvaluator中evaluate()返回的Piont对象值)到当前坐标值对象(currentPoint)
                    // 从而更新当前坐标值(currentPoint)

// 步骤4:每次赋值后就重新绘制,从而实现动画效果
                    invalidate();
                    // 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
                    // 所以坐标值每改变一次,就会调用onDraw()一次
                }
            });

            anim.start();
            // 启动动画


        } else {
            // 如果坐标值不为0,则画圆
            // 所以坐标值每改变一次,就会调用onDraw()一次,就会画一次圆,从而实现动画效果

            // 在该点画一个圆:圆心 = (30,30),半径 = 30
            float x = currentPoint.getX();
            float y = currentPoint.getY();
            canvas.drawCircle(x, y, RADIUS, mPaint);
        }
    }
}

参考文章:https://www.jianshu.com/p/2f19fe1e3ca1

Github示例代码


版权声明:本文为ws614054446原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。