前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >When Math meets Android Animation (3)

When Math meets Android Animation (3)

作者头像
宅男潇涧
发布2018-07-24 16:02:48
3980
发布2018-07-24 16:02:48
举报
文章被收录于专栏:潇涧技术专栏潇涧技术专栏

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

上一节我们得到一个重要的结论,借助TimeInterpolator或者TypeEvaluator“单独” 来控制动画所产生的动画效果殊途同归!

此外,上一节结尾我们还说到,项目AnimationEasingFunctions和项目EaseInterpolator本质上是差不多的,都是定义了一些动画效果对应的函数曲线。前者是将其封装成了TypeEvaluator,后者是将其封装成了Interpolator

这一节我们来研究下这些函数曲线。

1 缓动函数曲线

下图显示了常见的这些函数曲线,到底这些函数曲线都是什么鬼呢?

img
img

这些函数曲线最早是由Robert Penner提出来用于实现补间动画的"Penner easing functions",这些曲线主要分成10类,包括"BACK", "BOUNCE", "CIRCULAR", "ELASTIC", "EXPO", "QUAD", "CUBIC", "QUART", "QUINT", "SINE",每一类下面都有缓动进入、缓动退出以及缓动进入和退出三种效果,所以共有30个。这些效果对照着函数曲线来看其实也挺好理解,"QUAD", "CUBIC", "QUART", "QUINT"分别对应着二次、三次、四次以及五次曲线,"SINE"对应正弦函数曲线,"EXPO"对应指数函数曲线等等。其中"BACK""ELASTIC"有上冲和下冲的效果。

Robert Penner在Github上开源了jQuery的版本实现,随后也就有了很多不同语言版本的实现,例如Java版本的jesusgollonet/processing-penner-easing以及代码家的Android版本的AnimationEasingFunctions等等。

这些版本的实现都是4个参数的,分别是起始值b、数值间隔c(结束值-起始值)、当前时间t、时间间隔d

代码语言:javascript
复制
//不带缓动,也就是前面说的“线性”估值器
function noEasing (t, b, c, d) {
	return c * (t / d) + b;
}

//带缓动效果,例如二次曲线形式
easeInQuad: function (t, b, c, d) { //缓动进入
	return c*(t/=d)*t + b;
},
easeOutQuad: function (t, b, c, d) {//缓动退出
	return -c *(t/=d)*(t-2) + b;
},
easeInOutQuad: function (t, b, c, d) {//缓动进入和退出
	if ((t/=d/2) < 1) return c/2*t*t + b;
	return -c/2 * ((--t)*(t-2) - 1) + b;
},

那为什么与之殊途同归的EaseInterpolator是1个参数的呢?

代码语言:javascript
复制
//QuadInOut Interpolator
public float getInterpolation(float input) {
  if((input /= 0.5f) < 1) {
    return 0.5f * input * input;
  }
  return -0.5f * ((--input) * (input - 2) - 1);
}

这是因为当Interpolator传入到后面的TypeEvaluator的时候就有了起始值、结束值以及时间间隔(时间间隔定义在缓动函数内部,只有部分缓动函数需要这个参数)这3个参数,可以参考下面的代码来理解,所以说,它们在本质上还是一样的!

代码语言:javascript
复制
fraction = getInterpolation(input)  ==> 这种1个参数形式其实也可以等效于 easingfunction(currentTime, 0, 1, totalTime)
value = evaluate(fraction, startValue, endValue) = startValue + fraction * (endValue - startValue)  

2 One more thing

看到这里的话,我们就会想啦,如果我们把函数曲线抽象出来,然后再提供相应的转换方法,使其轻轻松松地转换成InterpolatorTypeEvaluator的话,如此,岂不善哉?

所以,我就站在众多巨人们的肩膀上,写了一个新项目Yava,项目代码非常简单,而且代码很少只有4个重要的类,它实现的功能就是将抽象的函数曲线轻松转换成立即可用的InterpolatorTypeEvaluator,并且提供了常见的30个缓动函数(Easing Functions)的实现,它们既可以当做Interpolator来用,又可以当做TypeEvaluator来用,非常方便。

这里我直接把这4个重要类的代码贴出来吧。

(1) IFunction接口

代码语言:javascript
复制
/**
 * 函数接口:给定输入,得到输出
 */
public interface IFunction {
    float getValue(float input);
}

(2)AbstractFunction抽象类

代码语言:javascript
复制
/**
 * 抽象函数实现,既可以当做简单函数使用,也可以当做Interpolator或者TypeEvaluator去用于制作动画
 */
public abstract class AbstractFunction implements IFunction, Interpolator, TypeEvaluator<Float> {

    @Override
    public float getInterpolation(float input) {
        return getValue(input);
    }

    @Override
    public Float evaluate(float fraction, Float startValue, Float endValue) {
        return startValue + getValue(fraction) * (endValue - startValue);
    }
}

(3)Functions

代码语言:javascript
复制
/**
 * 工具类,将自定义的函数快速封装成AbstractFunction
 */
class Functions {

    public static AbstractFunction with(final IFunction function) {
        return new AbstractFunction() {
            @Override
            public float getValue(float input) {
                return function.getValue(input);
            }
        };
    }
}

(4)EasingFunction枚举:包含了30个常见的缓动函数

代码语言:javascript
复制
/**
 * 常见的30个缓动函数的实现
 */
public enum EasingFunction implements IFunction, Interpolator, TypeEvaluator<Float> {

    /* ------------------------------------------------------------------------------------------- */
    /* BACK
    /* ------------------------------------------------------------------------------------------- */
    BACK_IN {
        @Override
        public float getValue(float input) {
            return input * input * ((1.70158f + 1) * input - 1.70158f);
        }
    },
    BACK_OUT {
        @Override
        public float getValue(float input) {
            return ((input = input - 1) * input * ((1.70158f + 1) * input + 1.70158f) + 1);
        }
    },
    BACK_INOUT {
        @Override
        public float getValue(float input) {
            float s = 1.70158f;
            if ((input *= 2) < 1) {
                return 0.5f * (input * input * (((s *= (1.525f)) + 1) * input - s));
            }
            return 0.5f * ((input -= 2) * input * (((s *= (1.525f)) + 1) * input + s) + 2);
        }
    },

    //other easing functions ......

    //如果这个function在求值的时候需要duration作为参数的话,那么可以通过setDuration来设置,否则使用默认值
    private float duration = 1000f;//目前只有ELASTIC***这三个是需要duration的,其他的都不需要

    public float getDuration() {
        return duration;
    }

    public EasingFunction setDuration(float duration) {
        this.duration = duration;
        return this;
    }

    //将Function当做Interpolator使用,默认的实现,不需要枚举元素去重新实现
    @Override
    public float getInterpolation(float input) {
        return getValue(input);
    }

    //将Function当做TypeEvaluator使用,默认的实现,不需要枚举元素去重新实现
    @Override
    public Float evaluate(float fraction, Float startValue, Float endValue) {
        return startValue + getValue(fraction) * (endValue - startValue);
    }

    //几个数学常量
    public static final float PI = (float) Math.PI;
    public static float TWO_PI = PI * 2.0f;
    public static float HALF_PI = PI * 0.5f;
}

这个项目的缓动函数的实现参考自EaseInterpolator中的实现,但是这个项目的代码和EaseInterpolator以及AnimationEasingFunctions这两个项目都完全不一样,非常简单易懂,既保留了原有项目应有的功能,同时为项目的使用场景提供了更多的可能,任何你想使用Interpolator或者TypeEvaluator都能使用它。

举个例子,以上一节中的弹跳动画效果为例,现在可以直接使用EasingFunction.BOUNCE_OUT作为Interpolator或者TypeEvaluator来使用:

第一种方式:使用线性插值器和自定义的TypeEvaluator

代码语言:javascript
复制
ObjectAnimator animator1 = new ObjectAnimator();
animator1.setTarget(textView1);
animator1.setPropertyName("translationY");
animator1.setFloatValues(0f, -100f);
animator1.setDuration(1000);
animator1.setInterpolator(new LinearInterpolator());
animator1.setEvaluator(EasingFunction.BOUNCE_OUT); //这里将EasingFunction.BOUNCE_OUT作为TypeEvaluator来使用
animator1.start();

第二种方式:使用自定义的Interpolator和”线性估值器”

代码语言:javascript
复制
ObjectAnimator animator2 = new ObjectAnimator();
animator2.setTarget(textView2);
animator2.setPropertyName("translationY");
animator2.setFloatValues(0f, -100f);
animator2.setDuration(1000);
animator2.setInterpolator(EasingFunction.BOUNCE_OUT); //这里将EasingFunction.BOUNCE_OUT作为Interpolator来使用
animator2.setEvaluator(new FloatEvaluator());
animator2.start();

如果你想使用自己定义的函数来制作动画,可以使用Functionswith方法,传入一个实现了IFunction接口的类就行,返回值你既可以当做Interpolator,也可以当做TypeEvaluator来使用

代码示例:

代码语言:javascript
复制
ObjectAnimator animator1 = new ObjectAnimator();
animator1.setTarget(textView1);
animator1.setPropertyName("translationY");
animator1.setFloatValues(0f, -100f);
animator1.setDuration(1000);
animator1.setInterpolator(new LinearInterpolator());
animator1.setEvaluator(Functions.with(new IFunction() { //自定义为TypeEvaluator
    @Override
    public float getValue(float input) {
        return input * 2 + 3;
    }
}));
animator1.start();

或者这样:

代码语言:javascript
复制
ObjectAnimator animator2 = new ObjectAnimator();
animator2.setTarget(textView2);
animator2.setPropertyName("translationY");
animator2.setFloatValues(0f, -100f);
animator2.setDuration(1000);
animator2.setInterpolator(Functions.with(new IFunction() { //自定义为Interpolator
    @Override
    public float getValue(float input) {
        return input * 2 + 3;
    }
}));
animator2.setEvaluator(new FloatEvaluator());
animator2.start();

为了方便查看定义出来的InterpolatorTypeEvaluator的效果,我将前面两个项目中的可视化部分整理到项目Yava中,样例应用还包含了上一节的用来作验证的例子,最终效果如下:

img
img

恭喜你终于看完了,也恭喜自己终于写完了。至此,你可能还有一个疑惑,那就是那些函数曲线是怎么想出来的?这个…我也不知道,我也想知道,别问我,去问Robert Penner吧 ?

最后,我还准备写另一个Android动画效果库wava,神一样的代码家还做了一个超厉害的项目AndroidViewAnimations,目前我的wava只是基于它做些改进,后期我打算加上一些很特别的东西,暂时不表,欢迎关注项目wava ?

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016/5/28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 缓动函数曲线
  • 2 One more thing
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档