文章出處

本文假定你已經對屬性動畫有了一定的了解,至少使用過屬性動畫。下面我們就從屬性動畫最簡單的使用開始。

    ObjectAnimator
      .ofInt(target,propName,values[])
      .setInterpolator(LinearInterpolator)
      .setEvaluator(IntEvaluator)
      .setDuration(500)
      .start();

相信這段代碼對你一定不陌生,代碼中有幾個地方是本文中將要重點關注的,setInterpolator(...)setEvaluator(...)setDuration(...)在源代碼中是如何被使用的。另外,我們也將重點關注Android中屬性動畫是如何一步步地實現動畫效果的(精確到每一幀(frame))。最后啰嗦幾句,本文中使用的代碼是Android 4.2.2。

上面代碼的作用就是生成一個屬性動畫,根據ofInt()我們知道只是一個屬性值類型為Int的View的動畫。先放過其他的函數,從ObjectAnimator的start()函數開始。

    public void  start() {
        //...省略不必要代碼 
        super.start();
    }

從代碼中我們知道,它調用了父類的start()方法,也就是ValueAnimator.start()。這個方法內部又調用了自身類內部的start(boolean playBackwards)方法。

    /**
     * 方法簡要介紹:
     * 這個方法開始播放動畫。這個start()方法使用一個boolean值playBackwards來判斷是否需
     * 要回放動畫。這個值通常為false,但當它從reverse()方法被調用的時候也
     * 可能為true。有一點需要注意的是,這個方法必須從UI主線程調用。
     */
    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mPlayingBackwards = playBackwards;
        mCurrentIteration = 0;
        mPlayingState = STOPPED;
        mStarted = true;
        mStartedDelay = false;
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        if (mStartDelay == 0) {
            // 在動畫實際運行前,設置動畫的初始值
            setCurrentPlayTime(0);
            mPlayingState = STOPPED;
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.start();
    }

對代碼中幾個值的解釋

  • mPlayingStated代表當前動畫的狀態。用于找出什么時候開始動畫(if state == STOPPED)。當然也用于在animator被調用了cancel()end()在動畫的最后一幀停止它。可能的值為STOPPED, RUNNING, SEEKED.
  • mStarted是Animator中一個額外用于標識播放狀態的值,用來指示這個動畫是否需要延時執行。
  • mStartedDelay指示這個動畫是否已經從startDelay中開始執行。
  • AnimationHandler animationHandler 是一個實現了Runnable接口的ValueAnimator內部類,暫時先放過,后面我們會具體談到。

從上面這段代碼中,我們了解到一個ValueAnimator有它自己的狀態(STOPPED, RUNNING, SEEKED),另外是否延時也影響ValueAnimator的執行。代碼的最后調用了animationHandler.start(),看來動畫就是從這里啟動的。別急,我們還沒初始化ValueAnimator呢,跟進setCurrentPlayTime(0)看看。

    public void setCurrentPlayTime(long playTime) {
        initAnimation();
        long currentTime = AnimationUtils.currentAnimationTimeMillis();
        if (mPlayingState != RUNNING) {
            mSeekTime = playTime;
            mPlayingState = SEEKED;
        }
        mStartTime = currentTime - playTime;
        doAnimationFrame(currentTime);
    }

這個函數在animation開始前,設置它的初始值。這個函數用于設置animation進度為指定時間點。playTime應該介于0到animation的總時間之間,包括animation重復執行的時候。如果animation還沒有開始,那么它會等到被設置這個時間后才開始。如果animation已經運行,那么setCurrentTime()會將當前的進度設置為這個值,然后從這個點繼續播放。

接下來讓我們看看initAnimation()

    void initAnimation() {
        if (!mInitialized) {
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].init();
            }
            mInitialized = true;
        }
    }

這個函數一看就覺得跟初始化動畫有關。這個函數在處理動畫的第一幀前就會被調用。如果startDelay不為0,這個函數就會在就會在延時結束后調用。它完成animation最終的初始化。

那么mValues是什么呢?還記得我們在文章的開頭介紹ObjectAnimator的使用吧?還有一個ofInt(T target, Property property, int... values)方法沒有介紹。官方文檔中對這個方法的解釋是:構造并返回一個在int類型的values數值之間ObjectAnimator對象。當values只有一個值的時候,這個值就作為animator的終點值。如果有兩個值的話,那么這兩個值就作為開始值和結束值。如果有超過兩個以上的值的話,那么這些值就作為開始值,作為animator運行的中間值,以及結束值。這些值將均勻地分配到animator的持續時間。

先中斷ObjectAnimator.start()流程的分析,回到開頭ObjectAnimator.ofInt(...)

接下來讓我們深入ofInt(...)的內部看看。

    public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setIntValues(values);
        return anim;
    }

對這個函數的解釋:

  • target 就是將要進行動畫的對象
  • propertyName 就是這個對象屬性將要進行動畫的屬性名
  • values 一組值。隨著時間的推移,動畫將根據這組值進行變化。

再看看anim.setIntValues這個函數

    public void setIntValues(int... values) {
        if (mValues == null || mValues.length == 0) {
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofInt(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
            }
        } else {
            super.setIntValues(values);
        }
    }

一開始的時候,mProperty肯定還沒有初始化,我們進去setValues(PropertyValuesHolder.ofInt(mPropertyName, values))看看。這里涉及到PropertyValuesHolder這個類。PropertyValuesHolder這個類擁有關于屬性的信息和動畫期間需要使用的值。PropertyValuesHolder對象可以用來和ObjectAnimator或ValueAnimator一起創建可以并行操作不PropertyValuesHolder同屬性的animator。

那么PropertyValuesHolder.ofInt()是干嘛用的呢?它通過傳入的屬性和values來構造并返回一個特定類型的PropertyValuesHolder對象(在這里是IntPropertyValuesHolder類型)。

    public static PropertyValuesHolder ofInt(String propertyName, int... values) {
        return new IntPropertyValuesHolder(propertyName, values);
    }

在IntPropertyValuesHolder內部

    public IntPropertyValuesHolder(String propertyName, int... values) {
        super(propertyName);
        setIntValues(values);
    }

    @Override
    public void setIntValues(int... values) {
        super.setIntValues(values);
        mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
    }

跳轉到父類(PropertyValuesHolder)的setIntValues

    public void setIntValues(int... values) {
        mValueType = int.class;
        mKeyframeSet = KeyframeSet.ofInt(values);
    }

這個函數其實跟我們前面介紹到的 PropertyValueHolder的構造函數相似,它就是設置動畫過程中需要的值。如果只有一個值,那么這個值就假定為animator的終點值,動畫的初始值會自動被推斷出來,通過對象的getter方法得到。當然,如果所有值都為空,那么同樣的這些值也會在動畫開始的時候也會自動被填上。這套自動推斷填值的機制只在PropertyValuesHolder對象跟ObjectAnimator一起使用的時候才有效,并且有一個能從propertyName自動推斷出的getter方法這些條件都成立的時候才能用,不然PropertyValuesHolder沒有辦法決定這些值是什么。
接下來我們看到KeyframeSet.ofInt(values)方法。KeyframeSet這個類持有Keyframe的集合,在一組給定的animator的關鍵幀(keyframe)中會被ValueAnimator用來計算值。這個類的訪問權限為包可見,因為這個類實現Keyframe怎么被存儲和使用的具體細節,外部不需要知道。

接下來我們看看KeyframeSet.ofInt(values)方法。

    public static KeyframeSet ofInt(int... values) {
        int numKeyframes = values.length;
        IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
            keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
        } else {
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
            }
        }
        return new IntKeyframeSet(keyframes);
    }

在這個方法里面,我們看見從最開始的ObjectAnimator.ofInt(target,propName,values[]),也就是我們在文章的開頭使用系統提供的動畫初始化函數中傳入的int數組,在這里得到了具體的使用。先不關心具體的使用Keyframe.ofInt(...)。從這里我們就可以知道原來Android SDK通過傳入的int[]的長度來決定animator中每個幀(frame)的值。具體的對傳入的int[]的使用可以參考文章里面對ObjectAnimator.ofInt(...)的介紹。在KeyframeSet.ofInt這個函數的最后一句話使用了IntKeyframeSet的構造函數來初始化這些Keyframe。

    public  IntKeyframeSet(IntKeyframe... keyframes) {
         super(keyframes);
    }

在IntKeyframeSet的構造函數中又調用父類KeyframeSet的構造函數來實現。

    public KeyframeSet(Keyframe... keyframes) {
        mNumKeyframes = keyframes.length;
        mKeyframes = new ArrayList<Keyframe>();
        mKeyframes.addAll(Arrays.asList(keyframes));
        mFirstKeyframe = mKeyframes.get(0);
        mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
        mInterpolator = mLastKeyframe.getInterpolator();
    }

從這個構造函數中我們又可以了解到剛剛初始化后的Keyframe數組的第一項和最后一項(也就是第一幀和最后一幀)得到了優先的待遇,作為在KeyframeSet中的字段,估計是為了后面計算動畫開始和結束的時候方便。

小結ObjectValue、PropertyValueHolder、KeyframeSet的關系

我們繞了很久,不知道是否把你弄暈了,這里稍稍總結一下。我們就不從調用的順序一步步分析下來了,太長了。我直接說說ObjectValue、PropertyValueHolder、KeyframeSet之間的關系。這三個類比較有特點的地方,ObjectAnimator無疑是對的API接口,ObjectAnimator持有PropertyValuesHolder作為存儲關于將要進行動畫的具體對象(通常是View類型的控件)的屬性和動畫期間需要的值。而PropertyValueHolder又使用KeyframeSet來保存animator從開始到結束期間關鍵幀的值。這下子我們就了解animator在執行期間用來存儲和使用的數據結構。廢話一下,從PropertyValueHolder、KeyframeSet這個兩個類的源代碼來看,這三個類的API的設計挺有技巧的,他們都是通過將具有特定類型的實現作為一個大的概況性的類的內部實現,通過這個大的抽象類提供對外的API(例如,PropertyValuesHolder.ofInt(...)的實現)。

回到ObjectAnimator.start()流程的分析

不知道是否把你上面你是否能清楚,反正不太影響下面對ObjectAnimator.start()流程的分析。從上面一段的分析我們了解到ValueAnimator.initAnimation()中的mValue是 PropertyValuesHolder類型的東西。在initAnimation()里mValues[i].init()初始化它們的估值器Evaluator

    void init() {
        if (mEvaluator == null) {
            // We already handle int and float automatically, but not their Object
            // equivalents
            mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                    (mValueType == Float.class) ? sFloatEvaluator :
                    null;
        }
        if (mEvaluator != null) {
            // KeyframeSet knows how to evaluate the common types - only give it a custom
            // evaluator if one has been set on this class
            mKeyframeSet.setEvaluator(mEvaluator);
        }
    }

mEvaluator當然也可以使用ObjectAnimator.setEvaluator(...)傳入;為空時,SDK根據mValueType為我們初始化特定類型的Evaluator。這樣我們的初始化就完成了。接下來,跳出initAnimation()回到
setCurrentPlayTime(...)

    public void setCurrentPlayTime(long playTime) {
        initAnimation();
        long currentTime = AnimationUtils.currentAnimationTimeMillis();
        if (mPlayingState != RUNNING) {
            mSeekTime = playTime;
            mPlayingState = SEEKED;
        }
        mStartTime = currentTime - playTime;
        doAnimationFrame(currentTime);
    }

對animator三種狀態STOPPED、RUNNING、SEEKED的解釋

  • static final int STOPPED    = 0; // 還沒開始播放
  • static final int RUNNING    = 1; // 正常播放中
  • static final int SEEKED     = 2; // 定位到一些時間值(Seeked to some time value)

對mSeekedTime、mStartTime的解釋

  • mSeekedTime 當setCurrentPlayTime()被調用的時候設置。如果為負數,animator還沒能定位到一個值。
  • mStartTime 第一次在animation.animateFrame()方法調用時使用。這個時間在第二次調用animateFrame()時用來確定運行時間(以及運行的分數值)

setCurrentPlayTime(...)中doAnimationFrame(currentTime) 之前的代碼其實都是對Animator的初始化。看來doAnimator(...)就是真正處理動畫幀的函數了。這個函數主要主要用來處理animator中的一幀,并在有必要的時候調整animator的開始時間。

    final boolean doAnimationFrame(long frameTime) {
        //對animator的開始時間和狀態進行調整
        if (mPlayingState == STOPPED) {
            mPlayingState = RUNNING;
            if (mSeekTime < 0) {
                mStartTime = frameTime;
            } else {
                mStartTime = frameTime - mSeekTime;
                // Now that we're playing, reset the seek time
                mSeekTime = -1;
            }
        }
        // The frame time might be before the start time during the first frame of
        // an animation.  The "current time" must always be on or after the start
        // time to avoid animating frames at negative time intervals.  In practice, this
        // is very rare and only happens when seeking backwards.
        final long currentTime = Math.max(frameTime, mStartTime);
        return animationFrame(currentTime);
    }

看來這個函數就是調整了一些參數,真正的處理函數還在animationFrame(...)中。我們跟進去看看。

    boolean animationFrame(long currentTime) {
        boolean done = false;
        switch (mPlayingState) {
        case RUNNING:
        case SEEKED:
            float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
            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 ? false : true;
                    }
                    mCurrentIteration += (int)fraction;
                    fraction = fraction % 1f;
                    mStartTime += mDuration;
                } else {
                    done = true;
                    fraction = Math.min(fraction, 1.0f);
                }
            }
            if (mPlayingBackwards) {
                fraction = 1f - fraction;
            }
            animateValue(fraction);
            break;
        }

        return done;
    }

這個內部函數對給定的animation的一個簡單的動畫幀進行處理。currentTime這個參數是由定時脈沖(先不要了解這個定時脈沖是什么,后面我們會涉及)通過handler發送過來的(當然也可能是初始化的時候,被程序調用的,就像我們的分析過程一樣),它用于計算animation已運行的時間,以及已經運行分數值。這個函數的返回值標識這個animation是否應該停止(在運行時間超過animation應該運行的總時長的時候,包括重復次數超過的情況)。

我們可以把這個函數里面的fraction簡單地理解成animator的進度條的當前的位置。if (fraction >= 1f) 注意到函數里面的這句話,當animator開始需要重復執行的時候,那么就需要執行這個if判斷里面的東西,這里面主要就是記錄和改變重復執行animator的一些狀態和變量。為了不讓這篇文章太復雜,我們這里就不進行分析了。通過最簡單的animator只執行一次的情況來分析。那么接下來就應該執行animateValue(fraction)了。

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

在每一個animator的幀,這個函數都會被調用,結合傳入的參數:已運行時間分數(fraction)。這個函數將已經運行的分數轉為interpolaterd分數,然后轉化成一個可用于動畫的值(通過evaluator、這個函數通常在animation update的時候調用,但是它也可能在end()函數調用的時候被調用,用來設置property的最終值)。

在這里我們需要理清一下Interpolaterd和evaluator之間的關系。

  • Interpolator:用來定義animator變化的速率。它讓基礎的動畫效果(漸變、拉伸、平移、旋轉)有加速、減速、重復等效果。在源代碼中,其實interpolation的作用就是根據某一個時間點來計算它的播放時間分數,具體見官方文檔
  • evaluator: evaluator全部繼承至TypeEvaluator接口,它只有一個evaluate()方法。它用來返回你要進行動畫的那個屬性在當前時間點所需要的屬性值。

我們可以把動畫的過程想象成是一部電影的播放,電影的播放中有進度條,Interpolator就是用來控制電影播放頻率,也就是快進快退要多少倍速。然后Evaluator根據Interpolator提供的值計算當前播放電影中的哪一個畫面,也就是進度條要處于什么位置。

這個函數分三步:

  1. 通過Interpolator計算出動畫運行時間的分數
  2. 變量ValueAnimator中的mValues[i].calculateValue(fraction)(也就是 PropertyValuesHolder對象數組)計算當前動畫的值
  3. 調用animation的onAnimationUpdate(...)通知animation更新的消息
        //PropertyValuesHolder.calculateValue(...)
        void calculateValue(float fraction) {
            mAnimatedValue = mKeyframeSet.getValue(fraction);
        }

        //mKeyframeSet.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();
                if (interpolator != null) {
                    fraction = interpolator.getInterpolation(fraction);
                }
                final float prevFraction = prevKeyframe.getFraction();
                float intervalFraction = (fraction - prevFraction) /
                    (nextKeyframe.getFraction() - prevFraction);
                return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                        nextKeyframe.getValue());
            }
            prevKeyframe = nextKeyframe;
        }
        // shouldn't reach here
        return mLastKeyframe.getValue();
    }

我們先只關注if (mNumKeyframes == 2)這種情況,因為這種情況最常見。還記的我們使用ObjectAnimator.ofInt(...)傳入的int[]數組嗎?我們一般就傳入動畫開始值和結束值,也就是這里的mNumKeyframes ==2 的情況。這里通過mEvaluator來計算,我們看看代碼(IntEvaluator.evaluate(...)的代碼)。

    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }

mEvaluator.evaluate(...)計算后,我們就返回到ValueAnimator.animateValue(...)中,再回退到ValueAnimator.setCurrentPlayTime(...)。最后回到ValueAnimator.start(boolean playBackwards)。終于解析完了setCurrentPlayTime(...)這個函數,總結一下:這個函數主要用來初始化動畫的值,當然這個初始化比我們想象中的復雜多了,它主要通過PropertyValuesHolder、Evaluator、Interpolator來進行值的初始化。PropertyValueHolder又通過KeyframeSet來存儲需要的值。

我們回到文章開頭介紹的ValueAnimator.start(boolean playBackwards)

    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mPlayingBackwards = playBackwards;
        mCurrentIteration = 0;
        mPlayingState = STOPPED;
        mStarted = true;
        mStartedDelay = false;
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        if (mStartDelay == 0) {
            // This sets the initial value of the animation, prior to actually starting it running
            setCurrentPlayTime(0);
            mPlayingState = STOPPED;
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.start();
    }

在setCurrentPlayTime(0)后,緊接著就通過notifyStartListeners()通知animation啟動的消息。最后通過animationHandler.start()去執行。animationHandler是一個AnimationHandler類型的對象,它實現了runable接口。

        //AnimationHandler.start()
        public void start() {
            scheduleAnimation();
        }
        //AnimationHandler.scheduleAnimation()
        private void scheduleAnimation() {
            if (!mAnimationScheduled) {
                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
                mAnimationScheduled = true;
            }
        }

        // Called by the Choreographer.
        @Override
        public void run() {
            mAnimationScheduled = false;
            doAnimationFrame(mChoreographer.getFrameTime());
        }

mHandler.start()最終就是通過mChoreographer.發送給UI系統。這個過程比較復雜,這里不介紹。我們僅僅需要知道,動畫中的一幀通過這種方式發送給UI系統后,在UI系統執行完一幀后,又會回調AnimationHandler.run()。那么其實這個過程就相當于,AnimationHandler.start()開始第一次動畫的執行→UI系統執行AnimationHandler.run()→UI系統執行完后,回調相關函數→再執行AnimationHandler.run().可以理解為AnimationHandler.run()會一直調用自身多次(當然這是由UI系統驅動的),直至動畫結束。

這個過程比較復雜,如果你感興趣,可以關注我的下一篇博客。

,>


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()