Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android動畫TimeInterpolator(插值器)和TypeEvaluator(估值器)分析

Android動畫TimeInterpolator(插值器)和TypeEvaluator(估值器)分析

編輯:關於Android編程

這篇文章繼續分析Android動畫的TimeInterpolator(插值器)和TypeEvaluator(估值器)的簡單實現,以及分別去自定義插值器和估值器。

一,TimeInterpolator(插值器)

在動畫的播放過程中Android中提供插值器來改變動畫的播放速率,采用不用的插值器來實現不同的播放效果。

所有的插值器都要去實現TimeInterpolator接口,TimeInterpolator接口代碼如下。

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()函數就好了,參數input是一個0到1之間的數。但是返回的值是可以大於1,也是可以小於0的,對於返回值倒是沒什麼特定的要求。

TimeInterpolator接口裡面getInterpolation函數的參數input,那我們肯定好奇這個參數是怎麼來的,為什麼他會是一個0-1之間的數值呢。到底表示的而是什麼呢。接下來就得簡單的來看下這個參數是怎麼來的了。
有了 Android屬性動畫ValueAnimator源碼簡單分析文章的分析,我們知道插值器的使用是在ValueAnimator類的animateValue()函數中使用的。animateValue()函數源碼如下。

    void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

看到第二行插值器的使用了哦。那接了下就得去知道animateValue()函數是在哪裡調用的了。 繼續看ValueAnimator類中的animationFrame()函數

    boolean animationFrame(long currentTime) {
        boolean done = false;
        switch (mPlayingState) {
        case RUNNING:
        case SEEKED:
            float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
            if (mDuration == 0 && mRepeatCount != INFINITE) {
                // Skip to the end
                mCurrentIteration = mRepeatCount;
                if (!mReversing) {
                    mPlayingBackwards = false;
                }
            }
            if (fraction >= 1f) {
                if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
                    // Time to repeat
                    if (mListeners != null) {
                        int numListeners = mListeners.size();
                        for (int i = 0; i < numListeners; ++i) {
                            mListeners.get(i).onAnimationRepeat(this);
                        }
                    }
                    if (mRepeatMode == REVERSE) {
                        mPlayingBackwards = !mPlayingBackwards;
                    }
                    mCurrentIteration += (int) fraction;
                    fraction = fraction % 1f;
                    mStartTime += mDuration;
                    // Note: We do not need to update the value of mStartTimeCommitted here
                    // since we just added a duration offset.
                } else {
                    done = true;
                    fraction = Math.min(fraction, 1.0f);
                }
            }
            if (mPlayingBackwards) {
                fraction = 1f - fraction;
            }
            animateValue(fraction);
            break;
        }

        return done;
    }

6行先拿到了一個fraction,float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f; 這個也好理解就是用動畫的當前時間減去動畫的開始時間在除以每次動畫的持續時間。得到了fraction。
14-35行 fraction>1 動畫多次播放了,並且播放的次數還沒完。27行 fraction = fraction % 1f; 多次播放也是分解從一次一次來看的,這個時候fraction就相當於是每次動畫過程中的線性速率0-1之間值的。
36-38行 如果是反向播放 1-fraction 這個好說。
39行 調用animateValue(fraction);

到這裡我們也就知道了TimeInterpolator接口裡面getInterpolation函數的參數input就是動畫每一次播放過程中的時間比例值確實是0-1之間的值。

提示:上面函數的調用過程大部分都是直接給出了結論,直接給出了在哪裡哪裡調用了 都是在 Android屬性動畫ValueAnimator源碼簡單分析的分析基礎之上給出來的。

接下來就該是TimeInterpolator(插值器)的使用了,兩種方式使用Android已經幫我們實現的插值器,使用自定義插值器。
1. Android已經實現的插值器
Android裡面已經默認實現了9種插值器,這9種插值器如下

這裡寫圖片描述

既然Android已經實現了這麼多種的插值器,我們肯定得好奇裡面具體的源碼是怎麼實現的吧。我們就來瞧瞧第一個插值器AccelerateDecelerateInterpolator的源碼實現,沒啥說的源碼先貼上來。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;"> /** * An interpolator where the rate of change starts and ends slowly but * accelerates through the middle. */ @HasNativeInterpolator public class AccelerateDecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { public AccelerateDecelerateInterpolator() { } @SuppressWarnings({"UnusedDeclaration"}) public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) { } public float getInterpolation(float input) { return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; } /** @hide */ @Override public long createNativeInterpolator() { return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator(); } }

這類看起來還是蠻簡單哈,我們就關心兩個地方,一個是構造函數一個是getInterpolation()函數。
構造函數兩個,第一個是是我們具體JAVA代碼中new的時候用到,第二個構造函數是在資源文件裡面使用的時候調用的(@android:anim/accelerate_decelerate_interpolator的時候會調用這個構造函數)。
getInterpolation()函數,根據AccelerateDecelerateInterpolator插值器的曲線圖得到對應的數學表達式(cos((x + 1) * PI) / 2.0) + 0.5,然後數學表達式轉換為代碼形式。
AccelerateDecelerateInterpolator的效果展現如下(例子代碼底下會給出)
這裡寫圖片描述

注:Android自帶的9種插值器的效果在例子代碼裡面都有一一的展現。
2. 自定義插值器
接下來我們就來動手實現一個自定義插值器的例子,我們來實現SpringInterpolator插值器。
第一步,確定我們要實現的插值器的曲線圖

這裡寫圖片描述

第二步,數學表達式
pow(2, -10 * x) * sin((x - factor / 4) * (2 * PI) / factor) + 1
factor = 0.4 這個我們在構造函數的時候指定

第三步 代碼實現(把數學表達式轉換為代碼)

public class SpringInterpolator implements Interpolator {

    private static final float DEFAULT_FACTOR = 0.4f;

    private float mFactor;

    public SpringInterpolator() {
        this(DEFAULT_FACTOR);
    }

    public SpringInterpolator(float factor) {
        mFactor = factor;
    }

    @Override
    public float getInterpolation(float input) {
        // pow(2, -10 * input) * sin((input - factor / 4) * (2 * PI) / factor) + 1
        return (float) (Math.pow(2, -10 * input) * Math.sin((input - mFactor / 4.0d) * (2.0d * Math.PI) / mFactor) + 1);

    }
}

第四步 具體效果

這裡寫圖片描述

參照http://www.tuicool.com/articles/3yANji自定義插值器裡面還實現了一種CubicHermiteInterpolator的插值器。CubicHermiteInterpolator插值器的兩個點可以通過 http://cubic-bezier.com/ 來獲取想要的數值。具體的效果也可以在例子代碼裡面看到。

二,TypeEvaluator(估值器)

動畫過程中TypeEvaluator(估值器)的作用:當我們ValueAnimator.ofObject()函數來做動畫效果的時候就會用到估值器了,估值器說白了就是用來確定在動畫過程中每時每刻動畫的具體值得換句話說就是確定ValueAnimator.getAnimatedValue()返回的具體對象類型啊。具體分析在下文給出。

所有的估值器都要實現TypeEvaluator接口,TypeEvaluator接口具體代碼如下。

public interface TypeEvaluator {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * fraction representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: result = x0 + t * (x1 - x0),
     * where x0 is startValue, x1 is endValue,
     * and t is fraction.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value.
     * @param endValue   The end value.
     * @return A linear interpolation between the start and end values, given the
     *         fraction parameter.
     */
    public T evaluate(float fraction, T startValue, T endValue);

}

TypeEvaluator接口只需要實現一個evaluate()函數就好了,這個函數有三個參數,fraction: 表示當前這段數值變化值得比例,startValue:表示當前這段數值變化的開始值,endValue: 表示當前這段數據變化的結束值。

看到這裡我們肯定的好奇TypeEvaluator(估值器)在哪裡用到了,估值出來的值去哪裡了呢。繼續在前面文章 Android屬性動畫ValueAnimator源碼簡單分析的基礎之上來看這個問題。
有了 Android屬性動畫ValueAnimator源碼簡單分析的分析我們知道估值器的使用是在KeyframeSet類裡面的getValue()函數裡面。
那就進KeyframeSet類裡面的getValue()代碼裡面去看看

    public Object getValue(float fraction) {
        // Special-case optimization for the common case of only two keyframes
        if (mNumKeyframes == 2) {
            if (mInterpolator != null) {
                fraction = mInterpolator.getInterpolation(fraction);
            }
            return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
                    mLastKeyframe.getValue());
        }
        if (fraction <= 0f) {
            final Keyframe nextKeyframe = mKeyframes.get(1);
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            final float prevFraction = mFirstKeyframe.getFraction();
            float intervalFraction = (fraction - prevFraction) /
                (nextKeyframe.getFraction() - prevFraction);
            return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
                    nextKeyframe.getValue());
        } else if (fraction >= 1f) {
            final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
            final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            final float prevFraction = prevKeyframe.getFraction();
            float intervalFraction = (fraction - prevFraction) /
                (mLastKeyframe.getFraction() - prevFraction);
            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                    mLastKeyframe.getValue());
        }
        Keyframe prevKeyframe = mFirstKeyframe;
        for (int i = 1; i < mNumKeyframes; ++i) {
            Keyframe nextKeyframe = mKeyframes.get(i);
            if (fraction < nextKeyframe.getFraction()) {
                final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
                final float prevFraction = prevKeyframe.getFraction();
                float intervalFraction = (fraction - prevFraction) /
                    (nextKeyframe.getFraction() - prevFraction);
                // Apply interpolator on the proportional duration.
                if (interpolator != null) {
                    intervalFraction = interpolator.getInterpolation(intervalFraction);
                }
                return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                        nextKeyframe.getValue());
            }
            prevKeyframe = nextKeyframe;
        }
        // shouldn't reach here
        return mLastKeyframe.getValue();
    }

對於這個函數的具體分析可以參考 Android屬性動畫ValueAnimator源碼簡單分析裡面對這個函數詳細的分析,這裡是想在多扯一點,當我們使用ValueAnimator.ofObject()的時候,是一定要去設置估值器的,其實從代碼的側面也是可以看出來得這個函數裡面使用的mEvaluator都是沒有null值判斷的。他不像IntKeyframeSet類裡面的getIntValue()函數的mEvaluator,IntKeyframeSet類裡面的getIntValue()函數的mEvaluator是有一個為null判斷的如果沒指定mEvaluator的時候會調用類似於IntEvaluator類的具體實現的。
估值器的使用地方我們知道了是KeyframeSet類裡面的getValue(),接下來我們就得知道KeyframeSet類裡面的getValue()在哪裡調用了吧。是在PropertyValuesHolder類的calculateValue()函數裡面使用了。

    void calculateValue(float fraction) {
        Object value = mKeyframes.getValue(fraction);
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }

第二行的 mKeyframes.getValue(fraction);調用到了getValue(),得到的值放在了mAnimatedValue裡面,要通過PropertyValuesHolder類的getAnimatedValue()函數來得這個值,接著我們的知道PropertyValuesHolder類的calculateValue()函數在哪裡用到了吧。ValueAnimator類的animateValue函數中調用了 看代碼

    void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

第6行 調用到了哦。mValues[i].calculateValue(fraction);
animateValue()這個函數在動畫的過程中一直會調用到的。所以PropertyValuesHolder類的getAnimatedValue函數得到的值隨著動畫的進行是會改變的。
PropertyValuesHolder類的getAnimatedValue()函數的返回值最最直觀的表現就是在ValueAnimator類的getAnimatedValue()函數中用到了。
ValueAnimator類的getAnimatedValue函數

    public Object getAnimatedValue() {
        if (mValues != null && mValues.length > 0) {
            return mValues[0].getAnimatedValue();
        }
        // Shouldn't get here; should always have values unless ValueAnimator was set up wrong
        return null;
    }

上面稀裡糊塗的扯了一堆總的來說呢就是想說明一點,最後通過估值器計算出來的值呢,最直接反映到的地方就是ValueAnimator類的getAnimatedValue()函數得到的值。(函數的正好是我們在ValueAnimator.AnimatorUpdateListener接口的onAnimationUpdate()函數裡面使用)。

提示:上面函數的調用過程大部分都是直接給出了結論,直接給出了某某函數在哪裡哪裡調用了 都是在 Android屬性動畫ValueAnimator源碼簡單分析的分析基礎之上給出來的。

接下來就該是TypeEvaluator(估值器)的使用了,兩種方式使用Android內部實現的估值器,使用自定義的估值器。
1. Android系統內置的估值器
Android裡面默認提供了如下幾種估值器 ArgbEvaluator, FloatArrayEvaluator, FloatEvaluator, IntArrayEvaluator, IntEvaluator, PointFEvaluator, RectEvaluator。
我們同樣挑一個來看看具體的源碼實現就挑ArgbEvaluator,具體源碼如下

public class ArgbEvaluator implements TypeEvaluator {
    private static final ArgbEvaluator sInstance = new ArgbEvaluator();

    /**
     * Returns an instance of ArgbEvaluator that may be used in
     * {@link ValueAnimator#setEvaluator(TypeEvaluator)}. The same instance may
     * be used in multiple Animators because it holds no state.
     * @return An instance of ArgbEvalutor.
     *
     * @hide
     */
    public static ArgbEvaluator getInstance() {
        return sInstance;
    }

    /**
     * This function returns the calculated in-between value for a color
     * given integers that represent the start and end values in the four
     * bytes of the 32-bit int. Each channel is separately linearly interpolated
     * and the resulting calculated values are recombined into the return value.
     *
     * @param fraction The fraction from the starting to the ending values
     * @param startValue A 32-bit int value representing colors in the
     * separate bytes of the parameter
     * @param endValue A 32-bit int value representing colors in the
     * separate bytes of the parameter
     * @return A value that is calculated to be the linearly interpolated
     * result, derived by separating the start and end values into separate
     * color channels and interpolating each one separately, recombining the
     * resulting values in the same way.
     */
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        int startInt = (Integer) startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endInt = (Integer) endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
                (int)((startR + (int)(fraction * (endR - startR))) << 16) |
                (int)((startG + (int)(fraction * (endG - startG))) << 8) |
                (int)((startB + (int)(fraction * (endB - startB))));
    }
}

看起來也是蠻簡單的,ArgbEvaluator和顏色有關系的,evaluate函數裡面分別對startValue和endValue做了拆分。int總共32位,8位8位的去拆分,分別對應A,R,G,B。 然後對A,R,G,B每個都做fraction轉換,在組合到一起形成一個int值。
2. 自定義估值器
自定義一個CharEvaluator的估值器,實現A-Z的變化,也是網上參考的別人的例子。
具體代碼如下

public class CharEvaluator implements TypeEvaluator {

    @Override
    public Character evaluate(float fraction, Character startValue, Character endValue) {
        int startInt = (int) startValue;
        int endInt = (int) endValue;
        int curInt = (int) (startInt + fraction * (endInt - startInt));
        return (char) curInt;
    }
}

具體效果如下:
這裡寫圖片描述

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved