当数学遇上动画(2)

当数学遇上动画:讲述ValueAnimatorTypeEvaluatorTimeInterpolator之间的恩恩怨怨(2)

上一节的结论是,ValueAnimator就是由TimeInterpolatorTypeEvaluator这两个简单函数组合而成的一个复合函数。如下图所示:

img

上一节我们还将TimeInterpolatorTypeEvaluator看作是工厂流水线上的两个小员工,那么ValueAnimator就是车间主管,TimeInterpolator这个小员工面对的是产品的半成品,他负责控制半成品输出到下一个生产线的速度,而下一个生产线上的小员工TypeEvaluator的任务就是打磨半成品得到成品,最后将成品输出。

本小节进一步深究TimeInterpolatorTypeEvaluator在动画实现过程中承担的作用以及它们之间的联系与差异。
还是先说结论,借助TimeInterpolator或者TypeEvaluator"单独"来控制动画所产生的动画效果殊途同归!

1 两种特殊情况下的ValueAnimator

(1)上一节提到过,假设TimeInterpolatorLinearInterpolator(线性插值器,f(t)=t),也就是说时间比率不被“篡改”的话,那么ValueAnimator对应的函数其实就简化成了TypeEvaluator函数(F=g(x,a,b)=g(f(t),a,b)=g(t,a,b)),即动画实际上只由TypeEvaluator来控制。

这里可以理解为,TimeInterpolator这个员工请假了,但是工厂为了不停止生产安排了一个自动机器人代替他的工作,它只会匀速地将半成品输入到下一个生产线。

(2)同理,我们假设TypeEvaluator“LinearTypeEvaluator”(线性估值器,并没有这个说法,所以加上引号,计算方式就是g(x,a,b)=a+x(b-a))的话,那么ValueAnimator对应的函数也可以简化,F=g(x,a,b)=g(f(t),a,b)=a+f(t)(b-a)**,即动画实际上只由TimeInterpolator来控制。

同样的,这里可以理解为,TypeEvaluator这个员工请假了,默认也有个自动机器人采用默认的操作将半成品加工成最终成品输出。

(3)综上所述,我们来思考上一节留下的问题,即TimeInterpolatorTypeEvaluator到底啥关系?
其实TimeInterpolator是用来控制动画速度的,而TypeEvaluator是用来控制动画中值的变化曲线的。
虽然它们本质的作用是不同的,但是它们两个既可以联手来控制动画,也可以"单独"来控制动画(并非真的单独,而是另一方有个默认操作)。
单独控制动画的典型例子就是上一节提到的EaseInterpolatorAnimationEasingFunctions,这两个项目对于制作动画起到殊途同归作用。

为什么说TimeInterpolatorTypeEvaluator对于制作动画有着殊途同归的作用呢?
不难想象,在某些定制的情况下,上面两种特殊情况下的构造出来的ValueAnimator所产生的动画效果是一样的!
那如何来验证我们的这个结论呢?我们可以通过构造两个不同的特殊情况下的ValueAnimator来验证。

下面的代码显示了两个ValueAnimator,都是在1s中内将float类型的数值从0变化到1。第一个ValueAnimator使用的是LinearInterpolator和自定义的TypeEvaluator,第二个ValueAnimator使用的是自定义的TimeInterpolator"LinearTypeEvaluator"。打印输出的是两个ValueAnimator每次值变化的时候的大小。

ValueAnimator animator1 = new ValueAnimator();
animator1.setFloatValues(0.0f, 1.0f);
animator1.setDuration(1000);
animator1.setInterpolator(new LinearInterpolator());//传入null也是LinearInterpolator
animator1.setEvaluator(new TypeEvaluator() {
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        return 100 * fraction;
    }
});
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Log.e("demo 1", "" + animation.getAnimatedValue());
    }
});

ValueAnimator animator2 = new ValueAnimator();
animator2.setFloatValues(0.0f, 1.0f);
animator2.setDuration(1000);
animator2.setInterpolator(new Interpolator() {
    @Override
    public float getInterpolation(float input) {
        return 100 * input;
    }
});
animator2.setEvaluator(new TypeEvaluator() {
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        return fraction;
    }
});
animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Log.e("demo 2", "" + animation.getAnimatedValue());
    }
});

animator1.start();
animator2.start();

打印输出的结果如下图所示,从图中可以看出,两个ValueAnimator的在真实时间序列中的输出结果是一样的,也就说明如果将它们作用在同一个View组件的某个属性上的话,那么产生的动画效果是完全一样的。例如,可以将两个ValueAnimator改成ObjectAnimator,并将其作用在两个不同的TextView的translationY属性上,你可以看到一样的动画效果。所以说,在特殊的单独控制动画的情况下,TimeInterpolatorTypeEvaluator对于制作动画有着殊途同归的作用。(注意结论的前提,那就是在我们理解了ValueAnimator内部动画原理之后自己定制的一些特殊情况,它们并非总是能够产生一样的动画效果)

img

2 简单动画实例分析:弹跳!

经过前面的分析,我们差不多理解了ValueAnimator是怎么借助TimeInterpolatorTypeEvaluator来实现动画的了。在实现动画的时候,为了简便,我们常常可以选择将TimeInterpolator设置为LinearInterpolator或者将TypeEvaluator设置为"LinearTypeEvaluator"这两种特殊的方式。

举个栗子!假设我们要来实现弹跳的动画效果。首先我们要确定一个弹跳效果的函数曲线,自己想不太好想,我们先来看看项目EaseInterpolator中的EaseBounceOutInterpolator内部表示的函数曲线的形态。如下图所示,它是一个分段函数,每个段内都是一个简单的二次曲线。如果将这个曲线作用在View组件的translationY属性上,那么组件将在垂直方向上来回地跳动从而就形成了弹跳的效果。

img

我们先看下EaseBounceOutInterpolator的核心方法getInterpolation的实现,它其实就是刻画了上面的函数曲线。

//传入的参数input就是动画的时间比率值fraction
public float getInterpolation(float input) {
  if (input < (1 / 2.75))
    return (7.5625f * input * input);
  else if (input < (2 / 2.75))
    return (7.5625f * (input -= (1.5f / 2.75f)) * input + 0.75f);
  else if (input < (2.5 / 2.75))
    return (7.5625f * (input -= (2.25f / 2.75f)) * input + 0.9375f);
  else
    return (7.5625f * (input -= (2.625f / 2.75f)) * input + 0.984375f);
}

我们再看下AnimationEasingFunctions项目中实现这个效果的Easing函数类BounceEaseOut,它继承自BaseEasingMethod,而BaseEasingMethod类实现了TypeEvaluator接口。
BounceEaseOut类整理出来得到的核心方法evaluate的实现如下:

@Override
public final Float evaluate(float fraction, Number startValue, Number endValue) {
    float t = mDuration * fraction;//已经过去的时间
    float b = startValue.floatValue();//起始值
    float c = endValue.floatValue() - startValue.floatValue();//结束值与起始值之间的差值
    float d = mDuration;//总的时间间隔,t/d 就是已经过去的时间占总时间间隔的比率

    if ((t /= d) < (1 / 2.75f)) {
        return c * (7.5625f * t * t) + b;
    } else if (t < (2 / 2.75f)) {
        return c * (7.5625f * (t -= (1.5f / 2.75f)) * t + .75f) + b;
    } else if (t < (2.5 / 2.75)) {
        return c * (7.5625f * (t -= (2.25f / 2.75f)) * t + .9375f) + b;
    } else {
        return c * (7.5625f * (t -= (2.625f / 2.75f)) * t + .984375f) + b;
    }
}

仔细看下这两个函数的实现很不难发现,如果EaseBounceOutInterpolator+"LinearEvaluator"(IntEvaluator或者FloatEvaluator)得到的结果与LinearInterpolator+BounceEaseOut(TypeEvaluator)得到的结果是一样的啊!我们可以再写个例子作用在两个View上看下效果。

例子代码,作用在两个不同的TextView上的两个不同的ObjectAnimator:

//第一个ObjectAnimator
final ObjectAnimator animator1 = new ObjectAnimator();
animator1.setTarget(textView1);
animator1.setPropertyName("translationY");
animator1.setFloatValues(0f, -100f);
animator1.setDuration(1000);
animator1.setInterpolator(new LinearInterpolator());//使用线性插值器
animator1.setEvaluator(new TypeEvaluator<Number>() {//自定义的TypeEvaluator
    @Override
    public Number evaluate(float fraction, Number startValue, Number endValue) {
        float t = animator1.getDuration() * fraction;//已经过去的时间
        float b = startValue.floatValue();//起始值
        float c = endValue.floatValue() - startValue.floatValue();//结束值与起始值之间的差值
        float d = animator1.getDuration();//总的时间间隔,t/d 就是已经过去的时间占总时间间隔的比率

        if ((t /= d) < (1 / 2.75f)) {
            return c * (7.5625f * t * t) + b;
        } else if (t < (2 / 2.75f)) {
            return c * (7.5625f * (t -= (1.5f / 2.75f)) * t + .75f) + b;
        } else if (t < (2.5 / 2.75)) {
            return c * (7.5625f * (t -= (2.25f / 2.75f)) * t + .9375f) + b;
        } else {
            return c * (7.5625f * (t -= (2.625f / 2.75f)) * t + .984375f) + b;
        }
    }
});
animator1.start();

//第二个ObjectAnimator
final ObjectAnimator animator2 = new ObjectAnimator();
animator2.setTarget(textView2);
animator2.setPropertyName("translationY");
animator2.setFloatValues(0f, -100f);
animator2.setDuration(1000);
animator2.setInterpolator(new TimeInterpolator() {//自定义的TimeInterpolator
    @Override
    public float getInterpolation(float input) {
        if (input < (1 / 2.75))
            return (7.5625f * input * input);
        else if (input < (2 / 2.75))
            return (7.5625f * (input -= (1.5f / 2.75f)) * input + 0.75f);
        else if (input < (2.5 / 2.75))
            return (7.5625f * (input -= (2.25f / 2.75f)) * input + 0.9375f);
        else
            return (7.5625f * (input -= (2.625f / 2.75f)) * input + 0.984375f);
    }
});
animator2.setEvaluator(new FloatEvaluator());//使用"线性估值器"
animator2.start();

接下来看下效果吧,两个动画效果一模一样,这下你相信我说的了吧?哈哈哈,殊途同归!

img

其他的动画效果也是一样的,也就是说项目AnimationEasingFunctions和项目EaseInterpolator本质上是差不多的,都是定义了一些动画效果对应的函数曲线。前者是将其封装成了TypeEvaluator,后者是将其封装成了Interpolator!
(注:Interpolator继承自TimeInterpolator,而且接口内部是空的,所以和TimeInterpolator本质是一样的,它是为了兼容低版本而添加的。)

本文只是想验证本文开头的结论,借助TimeInterpolator或者TypeEvaluator"单独"来控制动画所产生的动画效果殊途同归!当然,你肯定可以同时使用自定义的TimeInterpolator和自定义的TypeEvaluator结合来控制动画,但是很显然,这种情况下的动画不容易控制。
从数学中函数的角度上来说那就是复合函数肯定比简单函数复杂,我们解决问题的时候要可能化繁为简,所以自然会考虑将ValueAnimator这个复杂函数简化成特殊情况下的简单函数TimeInterpolator或者TypeEvaluator来处理对吧?

关于ValueAnimatorTimeInterpolatorTypeEvaluator之间的恩恩怨怨讲到这里其实已经讲得差不多了,猜猜下一节我会说啥?(≧▽≦)/

请继续看下一节

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

推荐阅读更多精彩内容