Android动画之属性动画插值器和Evaluator

419 阅读4分钟

与视图动画中的插值器类似,我们也可以为属性动画添加插值器。比如对于ValueAnimator.ofInt(0,400)的属性动画而言,如果添加的插值器为LinearInterpolator,表示动画时的值从0到400匀速变化,如果使用了 AccelerateInterpolator,表示动画时值从0到400加速变化,越到后面值变化越快。其他的插值器也是类似的道理。

属性动画中的插值器就是用来控制动画的区间值是以何种频率被计算出来的。

接下来我们会尝试如何自定义一个插值器。 首先我们先看一下系统的插值器是如何实现的。

package android.view.animation;

import android.content.Context;
import android.graphics.animation.HasNativeInterpolator;
import android.graphics.animation.NativeInterpolator;
import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;

/**
 * An interpolator where the rate of change is constant
 */
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolator {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

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

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactory.createLinearInterpolator();
    }
}

以上是系统线性插值器的实现方式,其内的关键方法是getInterpolation(float input)。

其中,参数input的取值范围是0到1,包含0和1,0表示动画开始状态,1表示动画的结束状态,0.5表示动画的中间状态,从0到1的变化过程可以理解为动画时间的匀速增加的百分比,他是一个时间的匀速度量值。

返回值表示当前动画的实际进度,比如在线性插值器中,动画是匀速变化的,所以直接返回入参就可以。

我们再来看下加速插值器的实现方式,这里只列出关键方法。

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

mFactor 默认为1.0f,返回值是入参的平方,请看下图,当input从0增加到1时,input的平方是的增加幅度是越来越大的,这样也就显示出了加速的效果。

image.png

接下来我们自定义一个插值器,源码如下:

import androidx.annotation.RequiresApi
import android.os.Build
import android.view.animation.BaseInterpolator

/**
 * 项目名称 AndroidViewBook
 * 创建时间 2022/1/16 8:14 下午
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
class MyInterpolator : BaseInterpolator() {
    override fun getInterpolation(input: Float): Float {
        return 1f - input
    }
}

只需要实现关键方法getInterpolation即可,通过代码可以发现,我们将进度颠倒。 我们定义下面的动画,来移动一个View。

val valueAnimator:ValueAnimator = ValueAnimator.ofInt(0,300)
valueAnimator.setDuration(5000)
//valueAnimator.interpolator = MyInterpolator()
valueAnimator.addUpdateListener {
    val currentValue:Int = (it.animatedValue as Int).toInt()
    imageView.layout(currentValue,
       currentValue,
       currentValue + imageView.width,
       currentValue + imageView.height)
}
valueAnimator.start()

我们先看不设置我们自定义的插值器的动画效果图:

normal.gif

动画开始时,会从左上角向右下角移动。但是当我们设置自定义的插值器后,请看下图:

mine.gif

可以看到动画进度完全颠倒了。

接下来我们思考一个问题,我们通过ValueAnimator.ofInt(0,300)定义了一个0到300的数值区间,在插值器中得到了当前动画所对应的数值的进度百分比,那么我们在addUpdateListener中拿到的具体的动画进度数值是在什么时机进行转换的呢?即如何根据[0,300]和插值器中某一时刻返回的0.5来得到150的动画数值呢?所以必然有一个地方会将当前数值进度百分比转换为进度区间中的具体值,完成这项工作的角色就是Evaluator,它其实是一个转换器。能把小数进度转换为具体的数值。

接下来我们看一下系统实现的Evaluator。

public class IntEvaluator implements TypeEvaluator<Integer> {
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

这是对应ofInt方法的默认的Evaluator,我们之前之所以没有显示的设置它,系统会默认设置。ofFloat函数的默认Evaluator则是FloatEvaluator。 在evaluate函数中,第一个参数fraction是从插值器中返回的数值进度,而startValue和endValue则分别代表在ofInt中设置的数值区间。比如fraction是0.5时,返回值是 0 + 0.5*(300-0) = 150。

下面我们实现一个简单的Evaluator。在默认的IntEvaluator的基础上增加了200。

class MyIntEvaluator: TypeEvaluator<Int> {
    override fun evaluate(fraction: Float, startValue: Int?, endValue: Int?): Int {
        val startInt = startValue!!
        return (300 + startInt + fraction * (endValue!! - startInt)).toInt()
    }
}

当我们在定义的动画中应用后,可以看到如下的动画效果,明显的比第一张动图中位置要靠下。

Untitled.gif

结论:

在插值器中,我们可以通过自定义插值器,更改返回的数值进度百分比来改变动画的数值位置,而在Evaluator中,我们可以通过改变数据进度对应的具体数值来改变动画的数值位置,也就是说,既可以通过改变插值器中的数据进度百分比来改变动画的位置,也可以改变Evaluator中数值进度百分比对应的具体数值来改变动画位置。

下面我们通过定义一个Evaluator来实现上面插值器倒序播放的动画效果。

override fun evaluate(fraction: Float, startValue: Int?, endValue: Int?): Int {
    val startInt = startValue!!
    return endValue!! - (startInt + fraction * (endValue!! - startInt)).toInt()
}

可以看到实现了和上面定义的插值器相同的动画效果。

qw.gif

除了IntErpolator和FloatEvaluator外,还存在这一个ArgbEvaluator,可以实现颜色值的过渡转换。

参考资料《Android自定义控件开发与实战》