Android屬性動畫基礎:你是否真的瞭解插值器(TimeInterpolator)

weixin_34249678發表於2018-05-31

  插值器和估值器是我們可以改變動畫更新值的兩個切入點,通過自定義插值器和估值器,我們可以隨意改變動畫更新時值的計算方式以滿足我們特定的需求。本文簡單介紹屬性動畫插值器(TimeInterpolator)。在讀此文前,如果您還不瞭解屬性動畫執行流程,建議您先看一下這篇文章,簡單瞭解一下:Android屬性動畫基礎之流程解析

首先,看一下TimeInterpolator原始碼:

/**
 * A time interpolator defines the rate of change of an animation. This allows animations
 * to have non-linear motion, such as acceleration and deceleration.
 */
public interface TimeInterpolator {

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);
}

  通過介面描述,可以知道,通過插值器可以更改動畫的變化速率,其實類似於視訊播放,我們可以快放也可以慢放,只不過快放和慢放的速率都是線性的。隨著執行時間的流逝,動畫不斷進行更新,即根據動畫執行的時間來更新其所操縱的數值或物件,getInterpolation(float input)方法中的input引數就是與動畫當前執行週期內執行時間相關的歸一化變數,取值範圍[0,1],這點在上一篇文章Android屬性動畫基礎之流程解析有所提及,只是礙於篇幅沒有詳細介紹,這篇文章會對其做較為詳盡的解析。getInterpolation(float input)方法所計算出的數值會直接作為時間因子參與動畫更新計算。我們先看一下方法api對input的描述,翻譯過來大概是:"input引數取值範圍[0,1],表示動畫當前所處的節點,0代表動畫開始,1代表動畫結束"。但是,可但是,但可是,這個描述其實是不嚴謹的,稍後我們分析input引數的數值計算方式就會知道為何這個描述是不嚴謹的。
  為了搞清楚上述input引數的計算方式,我們需要知道getInterpolation方法何時被觸發,不用想,肯定是計算更新之前被觸發的,這簡直是廢話,其實我們首先需要了解屬性動畫執行流程(請參考Android屬性動畫基礎之流程解析),這裡不做過多闡述,直接看相關程式碼(如對屬性動畫流程有疑問,:

1   boolean animateBasedOnTime(long currentTime) {
2       boolean done = false;
3       if (mRunning) {
4           final long scaledDuration = getScaledDuration();
5           final float fraction = scaledDuration > 0 ?
6                   (float)(currentTime - mStartTime) / scaledDuration : 1f;
7           final float lastFraction = mOverallFraction;
8           final boolean newIteration = (int) fraction > (int) lastFraction;
9           final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
10                   (mRepeatCount != INFINITE);
11            if (scaledDuration == 0) {
12                // 0 duration animator, ignore the repeat count and skip to the end
13                done = true;
14            } else if (newIteration && !lastIterationFinished) {
15                // Time to repeat
16                if (mListeners != null) {
17                    int numListeners = mListeners.size();
18                    for (int i = 0; i < numListeners; ++i) {
19                        mListeners.get(i).onAnimationRepeat(this);
20                    }
21                }
22            } else if (lastIterationFinished) {
23                done = true;
24            }
25            mOverallFraction = clampFraction(fraction);
26            float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
27            animateValue(currentIterationFraction);
28        }
29        return done;
30    }

31    private float clampFraction(float fraction) {
32        if (fraction < 0) {
33            fraction = 0;
34        } else if (mRepeatCount != INFINITE) {
35            fraction = Math.min(fraction, mRepeatCount + 1);
36        }
37        return fraction;
38    }

    /**
     * Calculates the fraction of the current iteration, taking into account whether the animation
     * should be played backwards. E.g. When the animation is played backwards in an iteration,
     * the fraction for that iteration will go from 1f to 0f.
     */
39    private float getCurrentIterationFraction(float fraction) {
40        fraction = clampFraction(fraction);
41        int iteration = getCurrentIteration(fraction);
42        float currentFraction = fraction - iteration;
43        return shouldPlayBackward(iteration) ? 1f - currentFraction : currentFraction;
44    }

    /**
     * Calculates the direction of animation playing (i.e. forward or backward), based on 1)
     * whether the entire animation is being reversed, 2) repeat mode applied to the current
     * iteration.
     */
45    private boolean shouldPlayBackward(int iteration) {
46          // 注意此處條件判斷
47        if (iteration > 0 && mRepeatMode == REVERSE &&(iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
48            // if we were seeked to some other iteration in a reversing animator,
49            // figure out the correct direction to start playing based on the iteration
50            if (mReversing) {
51                return (iteration % 2) == 0;
52            } else {
53                return (iteration % 2) != 0;
54            }
55        } else {
56            return mReversing;
57        }
58    }

    /**
     * This method is called with the elapsed fraction of the animation during every
     * animation frame. This function turns the elapsed fraction into an interpolated fraction
     * and then into an animated value (from the evaluator. The function is called mostly during
     * animation updates, but it is also called when the <code>end()</code>
     * function is called, to set the final value on the property.
     *
     * <p>Overrides of this method must call the superclass to perform the calculation
     * of the animated value.</p>
     *
     * @param fraction The elapsed fraction of the animation.
     */
    @CallSuper
      // 動畫更新計算方法
59    void animateValue(float fraction) {
60        fraction = mInterpolator.getInterpolation(fraction);
61        mCurrentFraction = fraction;
62        int numValues = mValues.length;
63        for (int i = 0; i < numValues; ++i) {
              // 動畫更新計算
64            mValues[i].calculateValue(fraction);
65        }
66        if (mUpdateListeners != null) {
67            int numListeners = mUpdateListeners.size();
68            for (int i = 0; i < numListeners; ++i) {
69                mUpdateListeners.get(i).onAnimationUpdate(this);
70            }
71        }
72    }

  先看一下animateBasedOnTime(long currentTime)方法第26行,再結合動畫計算方法animateValue(float fraction),可以知道,插值器getInterpolation(float fraction)方法接收引數就是第26行計算出來的currentIterationFraction ,下面我們就看看該值是如何計算的。根據原始碼,很明顯我們需要先了解第25行的mOverallFraction和fraction,這倆貨在Android屬性動畫基礎之流程解析中真的有做過說明,這裡再簡單說一下。fraction是當前時間currentTime與動畫開始時間mStartTime的差值與動畫後期的比值,不考慮邊界條件的話,其實就是動畫執行的整體時間進度(可能大於1哦,因為您可能會重複執行動畫)。那麼mOverallFraction是啥呢,它是根據fraction做邊界處理之後得到的值,也就是考慮邊界條件後的動畫整體執行時間進度,假設您設定動畫重複執行的次數為n,那麼mOverallFraction的最大值為n+1。
  接下來就要看第26行了,mOverallFraction作為引數傳入getCurrentIterationFraction(float fraction)方法得到currentIterationFraction,currentIterationFraction又作引數傳入插值器getInterpolation(float input)方法,看看getCurrentIterationFraction(float fraction)方法。定位到第41行,首先根據整體執行進度計算出動畫的迭代次數(已重複執行的次數)iteration,第42行,整體進度減掉已重複執行次數得到當前執行週期內的時間進度currentFraction(其實就是週期歸一化而已,取值範圍[0,1]),如果您按照插值器getInterpolation(float input)方法api的描述來理解,那麼currentFraction就應該是input引數的接收值,然而並不一定是~,因為input引數接收的值是第43行計算出來的,沒辦法,看一下shouldPlayBackward(int iteration)方法吧。
  先看第47至第54行程式碼,只解析方法內使用的引數的意義,第47行的條件判斷條件為真時,要求迭代次數即已重複執行的次數大於0;mRepeatMode(控制動畫第偶數次執行方式:倒序執行或正序執行,就像影片播放一樣,是從頭至尾還是從尾至頭)為REVERSE;已迭代次數小於等於目標重複次數。mReversing也是一個控制上述"影片"播放順序的東東,它控制的是當前動畫是否要在原來執行順序的基礎上做翻轉,該引數可通過reverse()方法更改。通過第43行程式碼及shouldPlayBackward(int iteration)方法原始碼,可以很明確的將,插值器getInterpolation(float input)方法所接收的引數值未必是當前動畫執行週期內真正的時間進度,當您需要倒序執行動畫的時候,input = 1-currentFraction = 1 * (1-currentFraction),正序執行時input = currentFraction。但是,該值的的確確是應該參與動畫更新計算的時間因子,這點並沒有問題。我們可以以影片播放來舉例說明,假設影片片長10s,共100幀,設播放的時間為t,那麼正序播放的情況下,應該播放第(int) (10 * t)幀,倒序播放的話應該播放 (int) (100 - 10 * t = 10 * (10 - t)),那麼現在你再看看,10 * (10 - t) 與上述1 * (1-currentFraction)有什麼區別?其實沒區別,非要說有區別,也就是週期是否歸一化而已,因為這裡的10就是影片週期。
  綜上所述,傳入插值器getInterpolation(float input)方法中的引數值,就是原本應該參與動畫更新計算的時間因子,但是就像影片播放一樣,我們想要快放或者慢放,怎麼辦?很明顯,將原本的時間因子"篡改一下"就好了,這就是getInterpolation(float input)所做的事情,該方法根據原本真實的時間因子,計算出一個新的時間因子,然後傳入animateValue(float fraction)方法參與最終的計算(見第27行)。比影片快放慢放更強大的是,我們可以隨意"篡改",非線性的都可以。
  到此為止,我們應該已經瞭解插值器的用途,下一篇文章將會介紹估值器(TypeEvaluator)

簡單示例gif如下


11451103-3e3131f54c49541f.gif

相關文章