當數學遇上動畫(3)

當數學遇上動畫:講述ValueAnimatorTypeEvaluatorTimeInterpolator之間的恩恩怨怨(3)

上一節我們得到一個重要的結論,借助TimeInterpolator或者TypeEvaluator"單獨" 來控制動畫所產生的動畫效果殊途同歸!

此外,上一節結尾我們還說到,項目AnimationEasingFunctions和項目EaseInterpolator本質上是差不多的,都是定義了一些動畫效果對應的函數曲線。前者是將其封裝成了TypeEvaluator,后者是將其封裝成了Interpolator

這一節我們來研究下這些函數曲線。

1 緩動函數曲線

下圖顯示了常見的這些函數曲線,到底這些函數曲線都是什么鬼呢?

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

//不帶緩動,也就是前面說的“線性”估值器
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個參數的呢?

//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個參數,可以參考下面的代碼來理解,所以說,它們在本質上還是一樣的!

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接口

/**
 * 函數接口:給定輸入,得到輸出
 */
public interface IFunction {
    float getValue(float input);
}

(2)AbstractFunction抽象類

/**
 * 抽象函數實現,既可以當做簡單函數使用,也可以當做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

/**
 * 工具類,將自定義的函數快速封裝成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個常見的緩動函數

/**
 * 常見的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

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和"線性估值器"

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來使用

代碼示例:

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();

或者這樣:

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

恭喜你終于看完了,也恭喜自己終于寫完了。至此,你可能還有一個疑惑,那就是那些函數曲線是怎么想出來的?這個...我也不知道,我也想知道,別問我,去問Robert Penner吧 ??

最后,我還準備寫另一個Android動畫效果庫wava,神一樣的代碼家還做了一個超厲害的項目AndroidViewAnimations,目前我的wava只是基于它做些改進,后期我打算加上一些很特別的東西,暫時不表,歡迎關注項目wava ??

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 當數學遇上動畫:講述ValueAnimator、TypeEvaluator和TimeInterpolator之間的...
    javayhu閱讀 734評論 0 3
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,556評論 25 708
  • 當數學遇上動畫:講述ValueAnimator、TypeEvaluator和TimeInterpolator之間的...
    javayhu閱讀 436評論 0 1
  • 1 背景 不能只分析源碼呀,分析的同時也要整理歸納基礎知識,剛好有人微博私信讓全面說說Android的動畫,所以今...
    未聞椛洺閱讀 2,758評論 0 10
  • 一天很短, 一腳油門的長度, 從清晨到正午, 來不及仔細欣賞, 就已經變換了不同的溫度。 歲月很短, 一場歡喜的長...
    紫雨true閱讀 512評論 1 4