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

重要结论:

既可以通过重写插值器改变数值进度来改变数值位置,
也可以通过改变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参数相当于时间的概念,
正如我们生活中参照时钟来知晓时间,
这里我们参照匀速、自然、不可改变的自然进度input
input类似于数学函数意义匀速增长自变量 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:
    我们通过监听器拿到的是当前动画所对应的具体数值,
    而不是用小数表示的数值。
    那么必须有一个地方会根据当前的数值进度将其转换为对应的数值
    这个地方就是Evaluator
    Evaluator用于将
    从插值器返回的数值进度(小数,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<Integer> {

    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参数就是插值器中的返回值,
    表示当前动画的数值进度,以百分制的小数表示。

  • startValueendValue分别对应
    ofInt(int start,int end)函数中startend的数值。


    假设当我们定义的动画ofInt(100,400)进行到数值进度20%的时候,
    那么此时在evaluate()函数中,
    fraction的值就是0.2
    startValue的值是100
    endValue的值是400

    返回值(即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)× 显示进度是相对应:

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



自定义Evaluator

public class MyEvaluator implements TypeEvaluator<Integer> {
    @Override
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int) (200 + startInt + fraction * (endValue - startInt));
    }
}
  • 首先实现TypeEvaluator接口;(注意这里的理解泛型的概念);
    这里实现TypeEvaluator时,指定它的泛型是Integer类型的,
    这样就可以在ofInt()函数中使用这个Evaluator了。

  • 注意:
    只有定义动画时的数值类型与Evaluator的返回值类型一样,
    才能使用这个Evaluator。
    很显然,ofInt()函数定义的数值类型是Integer,
    这里定义的MyEvaluator只能适用于ofInt()函数了。
    同理,
    如果把实现的TypeEvaluator接口泛型设置为Float类型,
    那么这个Evaluator也就只能适用于ofFloat()函数了。

  • 然后简单实现其中的evaluate()函数:

    在IntEvaluator的基础上修改了一下,
    让它返回值时增加了200。
    所以,
    当我们定义的区间是ofInt(0,400)时,
    它的实际返回值区间应该是(200,600)。

使用自定义的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();
    }



  • 自定义Evaluator实现倒序输出



  • IntEvaluatorFloatEvaluator外,
    android.animation包下还有另一个Evaluator
    名为ArgbEvaluator,它是用来实现颜色值过渡转换的。
  • ArgbEvaluator的实现原理

  • 这段代码分为三部分:
    第一部分根据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();
    }











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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容