自定义View---插值器Interpolator与Evaluator的详解及其自定义运用

146 阅读12分钟

重要结论:
既可以通过重写插值器改变数值进度来改变数值位置, 也可以通过改变Evaluator数值进度所对应的具体数值来改变数值位置。
自定义Interpolator自定义Evaluator, 这两种方法都可以成为属性动画的驱动力!!!

本文完整项目代码敬请见GitHub

#系统插值器种类 ####1.AccelerateDecelerateInterpolator 加速减速插值器, 表示在开始与结束的地方速率改变比较慢,在中间的时候加速。 利用数学绘图工具将它的整个变化过程绘制出来,如下图所示:

  • 整幅图像表示的是**动画进行的时刻(x轴)进行的程度(y轴)的关系图,** y轴的值域0 ~ 100,表示动画进行的程度; 最左侧(线的点坐标 y = 0)表示动画进度为0,即动画刚开始; 最右侧(线的点坐标 y = 100)表示动画完成,进度为1; 线上的每一个点表示在某个时间点动画进行的程度
    所以某个点切线斜率就表示 该点对应的时间点的动画速率; 该点的切线斜率越大, 该点对应的时间点的动画的速率则越大;
    下面就用这种图来演绎诸中系统插值器的特性;



####2.AccelerateInterpolator

  • 加速插值器,表示在动画开始的地方速率改变比较慢;
  • 动画一直加速,在停止时是突然停止的, 而不像AccelerateDecelerateInterpolator先减速再停止;



####3.DecelerateInterpolator 是减速插值器, 表示在动画开始的一瞬间加速到最大值,然后逐渐变慢



####4.LinearInterpolator 线性插值器,也称匀速加速器,很显然,它的速率是保持恒定的



####5.BounceInterpolator 弹跳插值器,模拟了控件自由落地后回弹的效果

  • 上面的4幅图像虽然速率有变化, 但是随着时间的推移,动画进度一直是增长的: 而在这幅图像中,在结束部分, 随着时间的推移,动画进度会回退。 这也就是出现回弹效果的原因,它把动画进度回退了。



####6. AnticipateInterpolator 初始偏移插值器,表示在动画开始的时候向前偏移一段距离,然后应用动画。

  • AnticipateInterpolator还有一个构造函数。 public AnticipateInterpolator(float tension) 参数float tension 对应的XML属性为android:tension,表示张力值,默认值为2, 值越大,初始的偏移量越大,而且速度越快; 当直接使用new AnticipateInterpolator()构造时,使用的是tension的默认值2。 下图展示了当tension(图中的T)分别取0.5、2、4时的数学图像。



####7.OvershootInterpolator 结束偏移插值器,表示在动画结束时,沿动画方向继续运动一段距离后再结束动画。

  • OvershootInterpolator也有另一个构造函数。 public OvershootInterpolator(float tension) 参数float tension 对应的XML属性为android:tension, 表示张力值,默认值为2,值越大,结束时的偏移量越大,而且速度越快;



####8. AnticipateOvershootInterpolator AnticipateInterpolator与OvershootInterpolator的合体, 即在动画开始时向前偏移一段距离,在动画结束时向后偏移一段距离。

  • AnticipateOvershootInterpolator也有其他的构造函数参数float tension对应的XML属性为android:tension, 表示张力值,默认值为2, 值越大,起始和结束时的偏移量越大,而且速度越快。
    参数float extraTension对应的XML属性为android:extraTension, 表示额外张力值,默认值为1.5。



####9. CycleInterpolator 循环插值器,表示动画循环播放特定的次数,速率沿正弦曲线改变。

  • 其构造函数: public CycleInterpolator(float cycles) 参数cycles表示循环次数;
  • android:fillAfter="true"属性 对于CycleInterpolator而言没有影响。

##案例:镜头由远及近效果

  • 实现其实就是一个ScaleAnimation缩放动画而已, 再配合一下插值器;
  • 完整项目代码敬请见GitHub
......
            case 6:
                ImageView cameraStretchView = new ImageView(this);
                cameraStretchView.setBackgroundResource(R.drawable.imagetest1);
                ll_nextParent.addView(cameraStretchView, layoutParams);

                ScaleAnimation scaleAnimation = getTheCameraStretchAnim();
                cameraStretchView.startAnimation(scaleAnimation);

                break;

            default:
        }
    }

    private ScaleAnimation getTheCameraStretchAnim() {
        ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 1.2f, 1.0f, 1.2f,
                Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
        scaleAnimation.setFillAfter(true);
        scaleAnimation.setInterpolator(new BounceInterpolator());
        scaleAnimation.setDuration(6000);
        return scaleAnimation;
    }



##自定义插值器 ######前言 - 通过`ofInt(0,400)`定 义 了 动 画 的 区 间 值 是 0~400, 然后通过添加`AnimatorUpdateListener`来监听动画的实时变化。
  • 那么0~400是怎么变化的呢? 插值器就是用来控制动画的区间值如何被计算出来的。 比如 LinearInterpolator插值器表示匀速返回区间内的值; 而DecelerateInterpolator插值器则表示开始变化快,后期变化慢; 其他插值器与此类似。

  • 在 ValueAnimator 中使用插值器很简单, 直接调用 ValueAnimator.setInterpolator(TimeInterpolator value)即可。

######先看看系统自带的插值器是如何实现的,比如LinearInterpolator

  • LinearInterpolator 实现了 Interpolator 接口, 而 Interpolator 接口则直接继承自TimeInterpolator, 而且并没有添加任何其他的方法。

######看看TimeInterpolator接口都有哪些函数

  • 里面只有一个函数 float getInterpolation(float input)

该函数的含义如下。

  • 参数input: input参数是Float类型的,它的取值范围是0~1, 表示当前动画的进度, 取0时表示动画刚开始, 取1时表示动画结束, 取0.5时表示动画中间的位置,其他以此类推。

  • 返回值(getInterpolation()return返回的东西): 表示当前实际想要显示的进度; 取值可以超过1,也可以小于0。 超过1表示已经超过目标值,小于0表示小于开始位置。

getInterpolation(flaot input)中的input参数 代表一个匀速增加的当前动画的进度, 这个进度是自然进度,自然的,没有经过修改的, 区分于实际显示的动画进度, 与任何设置无关,随着时间的推移, 动画的进度自然会从0到1逐渐增加。

input参数相当于时间的概念, 正如我们生活中参照时钟来知晓时间, 这里我们参照匀速、自然、不可改变的自然进度inputinput类似于数学函数意义匀速增长自变量 x), 通过向各种数学计算输入input各种数学计算则类似于数学函数意义中的函数关系f()), 以输出各种变化速率的数值作为返回值 (最后作为getInterpolation(flaot input)return返回值的数值, 则类似于因变量yy = f ( x ) ), 从而实现各种形式的插值器 (插值器就是基于函数规则, 把匀速增长的自变量input计算转换具有函数规则输出返回值)

input参数与任何我们设定的值没有关系, 只与时间有关,随着时间的推移,动画的进度也自然地增加,
input参数就代表了当前动画的自然进度, 而返回值则表示当前动画显示的数值进度

  • 返回值(getInterpolation()return返回的东西) 则表示动画的数值显示进度, 它可以通过Evaluator的计算,得到一个对应的数值范围, 对应的数值范围是我们通过ofInt()、ofFloat()函数来指定的。 如上例程, 在添加了AnimatorUpdateListener的监听事件以后, 通过在监听函数中调用animation.getAnimatedValue()函数, 就可以得到当前的值。
    当前的值=100+(400-100)× 显示进度 (这个公式其实就模拟了Evaluator的计算过程,Evaluator的内容稍后会进行详解) 其中, 100和400就是我们设置的ofInt(100,400)中的值, 而显示进度,就是返回值(getInterpolation()return返回的东西)

  • 通过上面的讲解, 我们知道了input参数与getInterpolation()函数返回值的关系(y = f(x)), 下面看看LinearInterpolator是如何重写TimeInterpolator的:

  • 以上, LinearInterpolator在getInterpolation()函数中直接把input值返回, 即以当前动画进度作为动画的数值进度, 这也就表示当前动画的数值进度与动画的时间进度一致。 由于动画进度是随时间匀速前进的, 所以LinearInterpolator的数值进度也是匀速增加的, 这便是基于LinearInterpolator的动画进度是匀速进行的原理;

####自定义Interpolator

  • 自定义插值器其实很容易, 只需实现TimeInterpolator接口, 重写getInterpolation(), 在里面返回自己需要的计算值和方式即可:

  • 下面例程, 自定义插值器的getInterpolation()函数中, 将进度反转过来, 当传入0的时候,让它的数值进度在完成的位置; 当完成的时候,让它的数值进度在开始的位置。

public class MyInterpolator implements TimeInterpolator {
    
    @Override
    public float getInterpolation(float input) {
        return 1 - input;
    }
}
  • 使用:GitHub文件目录
    private void startAnimationTestInterpolator() {
        ValueAnimator animator =  ValueAnimator.ofInt(0, 400);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (int)animation.getAnimatedValue();
                tv_text.layout(tv_text.getLeft(),curValue,
                        tv_text.getRight(), curValue+tv_text.getHeight());
            }
        });

        animator.setDuration(1000);
        animator.setInterpolator(new MyInterpolator());
        animator.start();
    }

>######接下来笔记Evaluator



##Evaluator

  • 下图讲述了 从定义动画的数值区间到在AnimatorUpdateListener中 得到当前动画所对应数值的整个过程:
  1. ofInt(0,400):表示指定动画的数值区间,从0运动到400。

  2. 插值器: 在动画开始后 通过插值器会返回当前动画进度所对应的数值进度, 这个数值进度是以小数表示的,如0.2。

  3. Evaluator: 我们通过监听器拿到的是当前动画所对应的具体数值, 而不是用小数表示的数值。 那么必须有一个地方会根据当前的数值进度将其转换为对应的数值, 这个地方就是EvaluatorEvaluator用于将 从插值器返回的数值进度(小数,0 - 1.0) 转换成对应的数值

  4. 监听器返回: 在AnimatorUpdateListener监听器中 使用animation.getAnimatedValue()函数 拿到Evaluator中返回的数值。

  • 插值器返回的小数值表示的是当前动画的数值进度, 这对于无论是使用ofFloat()函数 还是使用ofInt()函数定义的动画都是适用的。 因为无论是什么动画,它的进度必然在0~1之间。 0表示还没开始,1表示动画结束,这对于任何动画都是适用的。

  • Evaluator则不一样, 它把插值器返回的小数进度转换成当前数值进度所对应的值。
    如果使用ofInt()函数来定义动画, 动画中的值应该都是Integer类型的, 所对应的Evaluator在返回值时,必然返回Integer类型的值;
    如果使用ofFloat()函数来定义动画, 动画中的值都是Float类型的, Evaluator在返回值时,必然返回Float类型的值。

  • 所以,每种定义方式所对应的Evaluator必然是它专用的。 Evaluator专用的原因在于动画数值类型不一样, 在通过Evaluator返回时会报强转错误, 所以只有在动画数值类型一样时,所对应的Evaluator才能通用。 ofInt()函数对应的Evaluator类名为IntEvaluator, 而ofFloat()函数对应的Evaluator类名为FloatEvaluator

  • 通过animator.setEvaluator()函数来设置Evaluator, (下面例程第三行)
    private void startAnimationArgbEvaluator() {
        ValueAnimator animator =  ValueAnimator.ofInt(0xffffff00, 0xff0000ff);
        animator.setEvaluator(new ArgbEvaluator());//设置Evaluator
        animator.setDuration(3000);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (Integer) animation.getAnimatedValue();
                tv_text.setBackgroundColor(curValue);
            }
        });
        animator.start();
    }

可以使用ValueAnimator.ofInt()函数构造ValueAnimator, 显式设置了它所对应的IntEvaluator, 用来计算数值进度所对应的数值。
但其实, ofInt()ofFloat()都是系统直接提供的函数, 所以会有默认的插值器和Evaluator可供使用。 ofInt()函数的默认Evaluator 是IntEvaluator, 而ofFloat()函数的默认Evaluator则是FloatEvaluator


- ####Ctrl + 左键 看一下IntEvaluator的源码 ``` package android.animation;

public class IntEvaluator implements TypeEvaluator {

public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
    int startInt = startValue;
    return (int)(startInt + fraction * (endValue - startInt));
}

}

>在IntEvaluator中
只有一个函数
`evaluate(float fraction,Integer startValue,Integer endValue)`
>- `fraction`参数就是插值器中的返回值,
表示当前动画的数值进度,以百分制的小数表示。
>
>- **`startValue``endValue`分别对应
`ofInt(int start,int end)`函数中`start``end`的数值。**<br>
假设当我们定义的动画`ofInt(100,400)`进行到数值进度`20%`的时候,
那么此时在`evaluate()`函数中,
`fraction`的值就是`0.2``startValue`的值是`100``endValue`的值是`400`。<br>
**`返回值`(即` return (int)(startInt + fraction * (endValue - startInt));`)
就是`当前数值进度``对应的具体数值`,
这个数值就是
我们在`AnimatorUpdateListener`监听器中
通过`animation.getAnimatedValue()`函数得到的数值。**
>
>- `evaluate(float fraction,Integer startValue,Integer endValue)`函数
根据`return (int)(startInt + fraction * (endValue - startInt));`,
也就是根据进度数值来计算出具体数值,
这跟前面插值器中提到的`当前的值=100+(400-100)× 显示进度`是相对应:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f917693ab50c437685bbcfbf9e77dfef~tplv-k3u1fbpfcp-zoom-1.image)


>**结论:
既可以通过重写插值器改变`数值进度`来改变数值位置,
也可以通过改变Evaluator中`数值进度所对应的具体数值`来改变数值位置。
这两种方法都可以成为`属性动画的驱动力!!!`**

<br><br>
###自定义Evaluator

public class MyEvaluator implements TypeEvaluator { @Override public Integer evaluate(float fraction, Integer startValue, Integer endValue) { int startInt = startValue; return (int) (200 + startInt + fraction * (endValue - startInt)); } }

>- 首先实现TypeEvaluator接口;(**注意这里的理解[泛型的概念](http://blog.csdn.net/harvic880925/article/details/49872903)**);
这里实现TypeEvaluator时,指定它的**泛型**是Integer类型的,
这样就可以在ofInt()函数中使用这个Evaluator了。
>
>- **注意:
只有定义动画时的数值类型与Evaluator的返回值类型一样,
才能使用这个Evaluator。
很显然,ofInt()函数定义的数值类型是Integer,
这里定义的MyEvaluator`只能适用于ofInt()`函数了。
同理,
如果把实现的`TypeEvaluator`接口`泛型`设置为Float类型,
那么这个Evaluator也就`只能适用于ofFloat()`函数了。**
>- 然后简单实现其中的`evaluate()`函数:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3788e26893e848fe8c0c5a2daf1c29fe~tplv-k3u1fbpfcp-zoom-1.image)在IntEvaluator的基础上修改了一下,
让它返回值时增加了200。
所以,
**当我们定义的区间是ofInt(0400)时,**
>**它的实际返回值区间应该是(200600)。**
#####使用自定义的Evaluator
private void startAnimation() {

    ValueAnimator animator =  ValueAnimator.ofInt(0, 400);

    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int curValue = (int)animation.getAnimatedValue();
            tv_text.layout(tv_text.getLeft(),curValue,
                    tv_text.getRight(), curValue+tv_text.getHeight());
        }
    });

    animator.setDuration(1000);
    animator.setEvaluator(new MyEvaluator());
    animator.start();
}

<br><br>
- #####自定义Evaluator实现倒序输出![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/be36288b8e32459983065ff09e4f9063~tplv-k3u1fbpfcp-zoom-1.image)


<br><br>
-`IntEvaluator``FloatEvaluator`外,
在`android.animation`包下还有另一个`Evaluator`,
名为`ArgbEvaluator`,它是用来实现颜色值过渡转换的。



- ####`ArgbEvaluator`的实现原理![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5a8c76194b044d4d9077801f2c0eae95~tplv-k3u1fbpfcp-zoom-1.image)
- 这段代码分为三部分:
第一部分根据`startValue`求出A、R、G、B中各个色彩的初始值;
第二部分根据`endValue`求出A、R、G、B中各个色彩的结束值;
第三部分根据当前动画的百分比进度求出对应的数值。

>#####注意0xff是八个二进制位而已,刚好留下一个色彩的值!!

- #####使用ArgbEvaluator
private void startAnimationArgbEvaluator() {
    ValueAnimator animator =  ValueAnimator.ofInt(0xffffff00, 0xff0000ff);
    animator.setEvaluator(new ArgbEvaluator());//设置Evaluator
    animator.setDuration(3000);

    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int curValue = (Integer) animation.getAnimatedValue();
            tv_text.setBackgroundColor(curValue);
        }
    });
    animator.start();
}





<br><br><br>
<br><br>
<br><br>

---
- **[参考自《Android自定义控件开发入门与实战》](https://github.com/harvic/harvic_blg_share)**
>#### **本文完整项目代码敬请见[GitHub](https://github.com/aaLiweipeng/CustomViewTest)**