当数学遇上动画(1)

当数学遇上动画:讲述 ValueAnimator、TypeEvaluator和TimeInterpolator之间的恩恩怨怨(1)

其实关于 ValueAnimator 的内部工作原理大家也都清楚,本文只是选择从数学函数的角度来解析这个原理,方便理解。看完了本节之后我们就更加清楚如何借助 TypeEvaluator 和 TimeInterpolator 来帮助我们实现动画等知识。

本系列文章共有三篇,第一篇通过源码解析ValueAnimator类,第二篇通过实例解析TimeInterpolatorTypeEvaluator,第三篇分析常见动画背后的缓动函数,最后引出一个新的Android动画开发的辅助库Yava

1 Android动画基础知识

(1)狭义而言,动画一般就是指某个View组件的某个或者某些属性值在一段时间内不断变化的过程,这个变化过程往往有个起始值、结束值和一系列的中间值,ValueAnimator就是用来反映这个属性值变化过程的重要类,所以本文的介绍主要是以分析ValueAnimator为主。 (2)如果将属性值的变化过程看做一个数学函数的话,从动画效果上来看它是连续的,但实际上它还是离散的,因为它实际上也就是通过插入中间值(简称插值)从而”一帧一帧”完成动画的,那每一帧在哪里取,取多少呢?这也就是ValueAnimator类主要完成的作用。

那到底ValueAnimator是怎么控制属性值的变化过程的呢?答案是借助TimeInterpolatorTypeEvaluator来帮忙!TimeInterpolator用来控制在哪里取,而TypeEvaluator用来控制取多少。(注:取多少个点进行插值是不确定的,例如动画持续时间1s,可能取60,也可能取54、57或者58个中间点进行插值)

先说本小节结论,每一个ValueAnimator其实就是一个的TimeInterpolator和一个TypeEvaluator的结合体。从数学的角度来看,ValueAnimator就是由TimeInterpolatorTypeEvaluator这两个简单函数组合而成的一个复合函数。用图来表述如下:

img

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

2 结合源码解释函数形式

(1)假设TimeInterpolator是函数x=f(t),t表示动画已经完成的时间比率(例如动画的总时长是10s,已经过了4s了,那么t=0.4),所以t的取值范围是[0,1],0表示动画开始,1表示动画结束。该函数的返回值指的是动画实际插值的时间点,一般是0到1之间,但是也可以小于0(”下冲”)或者大于1(”上冲”)。 该函数的作用是把当前时间进度映射成另一个值,这样动画参照的时间由此被”篡改”,动画的速度由此被改变。 (后面还有详细介绍)

参考接口TimeInterpolator的定义:

(2)假设TypeEvaluator是函数y=g(x,a,b),x就是前面函数f(t)篡改之后的插值的时间点,a、b分别表示属性动画的起始值和结束值。 该函数的作用是通过起始值、结束值以及插值时间点来计算在该时间点的属性值应该是多少。

参考接口TypeEvaluator的定义:

(3)假设TimeInterpolatorTypeEvaluator是上面两个简单函数,那么ValueAnimator也是一个函数,它其实就是表示TimeInterpolator的函数x=f(t)和表示TypeEvaluator的函数y=g(x,a,b)结合而成的复合函数F=g(f(t),a,b)

参考ValueAnimatoranimateValue方法的定义:

3 通俗解析各个击破

3.1 关于ValueAnimator

(0)ValueAnimator就是一个的TypeEvaluator和一个TimeInterpolator的结合体,所以该类有两个方法分别用来设置动画的TypeEvaluatorTimeInterpolator。 (1)setInterpolator方法可以不调用,默认是加速减速插值器AccelerateDecelerateInterpolator,但是如果调用且传入的参数为null的话,那么就会被设置成线性插值器LinearInterpolator (暂时不清楚为什么要这样做)。 (2)setEvaluator方法也可以不调用,默认会根据属性值的类型设置一个IntEvaluator或者FloatEvaluator。后面会讲到这类TypeEvaluator可以看作是线性估值器"LinearTypeEvaluator"并没有这个说法,因故加上引号)。

参考ValueAnimator的部分源码:

(4)调用ValueAnimatorofInt方法时发生了什么 ValueAnimatorofInt方法是创建动画常用的方法,该方法会调用ValueAnimatorsetIntValues,其中调用了PropertyValuesHoldersetIntValues方法,里面又调用了KeyframeSetofInt方法用来得到动画的帧集合,该方法的实现如下:

KeyframeofInt方法的签名为Keyframe ofInt(float fraction, int value):前者就是动画已经完成的时间比率,后者是该帧的属性值,它表示在这个特定的时间比率对应的时刻,函数曲线会经过或者非常接近这个属性值(可能是没有经过,而只是很接近很接近,毕竟是曲线拟合嘛)。

上面得到的帧只是动画的几个核心帧,肯定不是动画的全部帧,那中间的其他帧是怎么计算的呢? 这个问题我们可以看下KeyframeSetgetValue方法,方法传入的参数就是动画的时间比率,返回值就是此帧的属性值。

举个简单的例子,下图中的ValueAnimatorTimeInterpolatorLinearInterpolator,它的TypeEvaluatorIntEvaluator,初始化的时候给定了5个数字,那么核心帧集合中有5帧,此时我们要求当fraction=0.4的时刻的value是多少。

img

3.2 关于TimeInterpolator

我们都知道时间是一秒一秒过去的,也就是线性的,匀速前进的。如果属性值从起始值到结束值是匀速变化的话,那么整个动画看起来就是慢慢地均匀地变化着。但是,如果我们想让动画变得很快或者变得很慢怎么办?答案是我们可以通过“篡改时间”来完成这个任务!这正是TimeInterpolator类的工作,它实际上就是一条函数曲线。

举个栗子!如下图所示,x轴表示时间的比率,y轴表示属性值。在不考虑TypeEvaluator的计算的情况下,假设属性值是从0变化到1,默认情况下线性插值器就和曲线y=x一样,在时间t的位置上的值为f(t)=t,当t=0.5的时刻传给TypeEvaluator的是t=0.5的时刻的值0.5。但是,当我们将TimeInterpolator设置为函数y=x^2或者y=x^(0.5)时,动画的效果就截然不同啦。在t=0.5的时刻,y=x^2=0.25 < 0.5,表示它将时间推迟了,传给TypeEvaluator的是0.25时刻的值0.25;而y=x^(0.5)=0.71 > 0.5,表示它将时间提前了,传给TypeEvaluator的是0.71时刻的值0.71

此外,仔细观察曲线的斜率不难发现,曲线y=x^2的斜率在不断增加,说明变化越来越快,作用在View组件上就是刚开始挺慢的,然后不断加速的效果;而曲线y=x^(0.5)的斜率在不断减小,说明变化越来越慢,作用在View组件上就是刚开始挺快的,然后不断减速的效果。

img

推荐在cubic-bezier.com网站中简单绘制和上面两个曲线类似形状的曲线,然后选择线性曲线作为参考,查看方块的运动变化情况。

Android的动画框架中已经给我们提供了不少实现了TimeInterpolator的插值器,包括AccelerateDecelerateInterpolator, AccelerateInterpolator, AnticipateInterpolator, AnticipateOvershootInterpolator, BounceInterpolator, CycleInterpolator, DecelerateInterpolator, LinearInterpolator, OvershootInterpolator, PathInterpolator。

基本上每个插值器其实就对应一条曲线,例如加速减速插值器AccelerateDecelerateInterpolator对应的曲线如下,斜率是先增加后减小。

img

那除了Android系统自带的这些,还有哪些常见的TimeInterpolator呢?

不妨看看这个项目EaseInterpolator,作者实现了30种常见动画的TimeInterpolator,每个TimeInterpolator的曲线形状大致如下图所示 (图片截自easings.net,项目EaseInterpolator实现了其中的30个效果) 这个项目中的Ease[XXX]Interpolator都实现了Interpolator接口,而Interpolator接口继承自TimeInterpolator接口。

img

3.3 关于TypeEvaluator

TypeEvaluator实际上也是一条函数曲线,它的输入是TimeInterpolator传进来的被“篡改”了的时间比率,还有动画的起始值和结束值信息,输出就是动画当前应该更新的属性值。假设TimeInterpolatorLinearInterpolatorf(t)=t),也就是说时间比率不被“篡改”的话,那么ValueAnimator对应的函数其实就简化成了TypeEvaluator函数(F=g(x,a,b)=g(f(t),a,b)=g(t,a,b)),即动画实际上只由TypeEvaluator来控制。

Android系统动画框架中提供了几个TypeEvaluator,例如IntEvaluator、FloatEvaluator、ArgbEvaluator、PointEvaluator、PathEvaluator等

IntEvaluatorevaluate方法的实现:(这类TypeEvaluator就是下一节提到的线性估值器"LinearTypeEvaluator"

那除了Android系统自带的这些,还有哪些常见的TypeEvaluator呢?

这个时候不妨看看@代码家的经典项目AnimationEasingFunctions,里面实现了28种常见动画的TypeEvaluator,每个TypeEvaluator的曲线形状和上面的截自easings.net的图片一样,代码家实现的动画效果也正是参考自那个项目的效果而想出来的。 项目中的[XXX]Ease[YYY]都继承自BaseEasingMethod,而BaseEasingMethod实现了TypeEvaluator接口。

看到这里的话,机智的你肯定发现了,为什么那些TimeInterpolatorTypeEvaluator的函数曲线形状一样一样的,到底TimeInterpolatorTypeEvaluator是啥关系啊?在实现动画上它们又有啥区别呢?

请继续看下一节

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

3 5 收藏 评论

关于作者:五道口宅男潇涧

平凡之路,平凡如我! 个人主页 · 我的文章 · 5 ·    

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部