模拟自然动画的精髓——TimeInterpolator与Type
模拟自然动画的精髓——TimeInterpolator与TypeEvaluator
在今天的文章开始之前,有个忙想请大家帮一下,希望在京东、淘宝、当当、亚马逊购买了我的书《Android群英传:神兵利器》的朋友们,帮忙去网店上给个简短的评价,举手之劳,还是多谢大家啦~~
通过属性动画,我们可以模拟各种属性的动画效果,但对于这些属性来说,动画变化的速率和范围,是实现一个更加『真实、自然』的动画的基础,这两件事情,就是通过TimeInterpolator与TypeEvaluator来实现的。
TimeInterpolator与TypeEvaluator共同作用在ValueAnimator上,通过复合的方式产生最后的数据,这也就是数学上的『复合函数』,TimeInterpolator控制在何时取值,而TypeEvaluator控制在当前时间点需要取多少值。
由于这里涉及到两个变量,所以,这里我们通常使用『控制变量法』来进行这两个属性的研究,因为通常情况下,这两个属性的作用效果是殊途同归的。
TimeInterpolator
首先,我们研究TimeInterpolator,所以,将TypeEvaluator设置为默认,不产生任何修改。
TimeInterpolator,中文常常翻译成插值器。一个最简单的属性动画,示例如下:
ObjectAnimator animator = ObjectAnimator.ofFloat(mTextView, "translationX", 0, mDistance);
animator.setDuration(mDuration);
animator.setInterpolator(new BounceInterpolator());
animator.start();
通过setInterpolator方法,可以给Animator设置插值器,默认的插值器是AccelerateDecelerateInterpolator,即加速减速插值器。
理解TimeInterpolator的作用原理
TimeInterpolator是作用在时间参数上,例如我们有一个动画,时间从0到1,取值也从0到1,我们通过下面三条曲线来看同一时间点,取到的数值的不同。
1.png当时间取0.5时,我们对应的y=x这条曲线,取出的是0.5,y=sqrt(x)这条曲线,取出的是0.25,y=x^2 这条曲线,取出的是0.7。也就是说,同一个真实的时间节点0.5,我们通过设置不同的函数曲线,取出了不同的数值,那么TimeInterpolator正是通过这种方式,来对时间参数进行修改,即,真实的时间0.5,对于其它两个函数,分别取出了模拟时间0.25和0.7所对应的值,从而达到了『篡改』时间的目的。
Android中的TimeInterpolator
Android中已经给我们实现了很多TimeInterpolator,例如前面我们举的例子——AccelerateDecelerateInterpolator。我们打开AccelerateDecelerateInterpolator的源码。
2.png其中关键的就是那行数学公式——(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f。我们来绘制下这个公式对应的曲线图(这里input的取值范围是0到1)。
3.png在[0,1]区间内,就是我们的加速减速插值器了,结合字面意义很好理解。
那么在Android中,系统还给我们提供了非常多的TimeInterpolator,例如:AccelerateDecelerateInterpolator, AccelerateInterpolator, AnticipateInterpolator, AnticipateOvershootInterpolator, BounceInterpolator, CycleInterpolator, DecelerateInterpolator, LinearInterpolator, OvershootInterpolator, PathInterpolator。
大家可以通过API文档来找到这些插值器的定义,同时,通过源码来查看他们使用的数学公式。
自定义TimeInterpolator
自定义TimeInterpolator非常简单,我们参考系统自带的TimeInterpolator就可以实现了,即实现Interpolator接口和getInterpolation方法即可,例如:
package com.xys.naturalanim.views.interpolator;
import android.view.animation.Interpolator;
public class CustomInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
return (float) Math.sin((input) * Math.PI * 0.5F);
}
}
其重点就是实现getInterpolation方法中的数学公式。
TypeEvaluator
TypeEvaluator通常被翻译成估值器,在理解了TimeInterpolator之后,再理解TypeEvaluator就很简单了,一个系统自带的简单TypeEvaluator如下:
4.png可见,它和TimeInterpolator基本一样,只不过实现的公式的参数不一样,但简单的换算一下,就通用了,例如:
if (mInterpolator != null) {
for (int i = 0; i < mViewWidth; i++) {
mPath.lineTo(i, mViewHeight - mInterpolator.getInterpolation(i * 1.0F / mViewHeight) * mViewHeight);
}
} else {
for (int i = 0; i < mViewWidth; i++) {
mPath.lineTo(i, mViewHeight - (Integer) mTypeEvaluator.evaluate(i * 1.0F / mViewHeight, 0, mViewHeight));
}
}
但是它们还是有一些细小的区别的,后面再细说,简单的概括,就是:
TimeInterpolator控制动画的速度,而TypeEvaluator控制动画的值,他们可以共同作用,也可以单独作用(让另一个使用默认值)。
实际上,TypeEvaluator中的一个参数fraction,就是『复合函数』中TimeInterpolator计算的结果。即fraction=getInterpolation()。
自定义TypeEvaluator<Number>
这里首先讲一下TypeEvaluator<Number>的自定义,那么为什么要加<Number>呢,这是因为,这种方式限定了TypeEvaluator的类型是Number,那么这种就和TimeInterpolator几乎可以完全转化了,他们的目的都是通过提供的参数来完成曲线的绘制,从而实现对动画运动的控制。而TimeInterpolator只有一个参数,实现起来更加的简单,所以,大部分时候,我们都通过TimeInterpolator来实现这种运动曲线的模拟,所以,TypeEvaluator<Number>就这样没落了。
但是,不要以为TypeEvaluator就这样没用了,我们在小标题中也写了,是<Number>类型的TypeEvaluator可以进行转换,而TypeEvaluator实际上还有很多其它类型,在动画的坐标控制上,有奇效。
TypeEvaluator<PointF>控制点的坐标
前面我们说了,Float类型的TypeEvaluator和TimeInterpolator基本是一样的,但TypeEvaluator并不只有Float这样一种,它有一种用的比较多的特性,就是通过TypeEvaluator<PointF>来对运动坐标进行修改,将原本的直线坐标修改成曲线坐标,它通常会与ValueAnimator进行配合使用,例如下面的这个例子:
5.gif这种实现曲线运动的方式,就是通过TypeEvaluator<PointF>来进行实现的,其中核心原理,就是通过Bezier曲线的De Casteljau算法计算出具体的点坐标,并设置给TypeEvaluator,代码如下所示。
public class BezierEvaluator implements TypeEvaluator<PointF> {
private PointF mControlPoint;
public BezierEvaluator(PointF controlPoint) {
this.mControlPoint = controlPoint;
}
@Override
public PointF evaluate(float t, PointF startValue, PointF endValue) {
return BezierUtil.CalculateBezierPointForQuadratic(t, startValue, mControlPoint, endValue);
}
}
Bezier的计算公式如下所示。
/**
* B(t) = (1 - t)^2 * P0 + 2t * (1 - t) * P1 + t^2 * P2, t ∈ [0,1]
*
* @param t 曲线长度比例
* @param p0 起始点
* @param p1 控制点
* @param p2 终止点
* @return t对应的点
*/
public static PointF CalculateBezierPointForQuadratic(float t, PointF p0, PointF p1, PointF p2) {
PointF point = new PointF();
float temp = 1 - t;
point.x = temp * temp * p0.x + 2 * t * temp * p1.x + t * t * p2.x;
point.y = temp * temp * p0.y + 2 * t * temp * p1.y + t * t * p2.y;
return point;
}
所以,综上所述,在作动画速率曲线控制的时候,使用TimeInterpolator即可,如果要改变点的坐标,就可以使用TypeEvaluator。
自然动画
在了解了TimeInterpolator和TypeEvaluator之后,我们就可以来了解下动画展现的优化方式了,普通的动画默认以线性的方式展现,但带来的后果就是动画效果的『僵硬』,动画本来是模拟两个状态的过渡过程的,这个在自然界中是『自然、流畅』的,所以,我们不能通过线性的数据变化来模拟自然动画,这就需要使用TimeInterpolator和TypeEvaluator来设计动画曲线了,通过它们来控制动画的实现过程,从而实现动画的展示,这就是我们来实现自然动画的的基本方式。
缓动函数
既然线性的动画曲线无法满足我们的动画模拟需求,那么就需要通过一定的数学公式来改变这些动画曲线,值得庆幸的是,这些事情有人帮我们做过了,有人专门设计了这样一些动画的曲线库。
6.png就是这样一些缓动函数库,让我们在设计动画的时候,可以作更加真实的模拟。同时,你也可以设计自己的曲线函数,下面这个网站,就可以实现这样的模拟。
http://inloop.github.io/interpolator/
自然动画的模拟演示
在各位前辈的肩膀上,我这里撸了一个演示的Demo库,界面如图。
8.png这里主要有几个功能:
- 可以选择不同的TimeInterpolator
- 可以选择要演示的动画效果,包括位移、缩放、旋转、透明度
- 演示包含两个View,上面的是设置对应动画模拟效果的View,下面的是对照的线性效果的View
一个动态图简单的了解下:
7.gif代码已经开源到Github:
https://github.com/xuyisheng/NaturalAnim
欢迎大家提交自己的函数曲线-插值器。
欢迎大家关注我的公众号:Android群英传