Android原始碼解析(一)動畫篇-- Animator屬性動畫系統

PigCanFly發表於2017-11-13

Android原始碼解析-動畫篇

Android在3.0版本中引入了新的動畫實現:屬性動畫。我們一般稱之為Animator。這種動畫通過變更控制元件屬性達到動畫效果。其中,屬性動畫最重要的一點,就是控制了動畫的時序,我們不妨來看下屬性動畫的簡單用法:

//code1
ValueAnimator animator = ValueAnimator.ofInt(0,100)//line1
                .setDuration(100);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
                        Log.d("animator-demo","onAnimationUpdate "+valueAnimator.getAnimatedValue());
                    }
                });
        animator.start();
複製程式碼

code1非常簡單,就是定義了這樣的ValueAnimator物件: 1.物件的過渡區間設定為 0~100 2.設定了動畫時間 100 3.設定了一個AnimatorUpdateListener監聽器 我們通過列印***animator-demo***日誌可以得到:

11-08 01:21:00.594 29311 29311 D animator-demo: onAnimationUpdate 0
11-08 01:21:00.606 29311 29311 D animator-demo: onAnimationUpdate 0
11-08 01:21:00.626 29311 29311 D animator-demo: onAnimationUpdate 7
11-08 01:21:00.644 29311 29311 D animator-demo: onAnimationUpdate 30
11-08 01:21:00.664 29311 29311 D animator-demo: onAnimationUpdate 59
11-08 01:21:00.681 29311 29311 D animator-demo: onAnimationUpdate 84
11-08 01:21:00.701 29311 29311 D animator-demo: onAnimationUpdate 98
11-08 01:21:00.718 29311 29311 D animator-demo:
onAnimationUpdate 100
複製程式碼

可以看出,通過ValueAnimator我們很平滑的從0過渡到了100,而並不關心其中的時序和數值的對應關係。ValueAnimator是整個屬性動畫的基礎,因此本章將重點分析ValueAnimator的內部機制。我們先來看下ValueAnimator的繼承關係圖:

屬性動畫相關類圖

ValueAnimator繼承於Animator,在Animator中只是定義了一些常用介面和一些基礎 Api,比如:

  1. AnimatorListener回撥
  2. start() , end() 函式等 此外,ValueAnimator還實現了介面AnimationFrameCallback。這個介面是在AnimationHandler類中定義的回撥介面,這個介面,我們一會兒會提到,它跟我們的動畫息息相關。

有了以上的知識儲備,我們可以開始我們下一步,我們要使用 ValueAnimator ,就需要構造它,上面的code1例子中我們使用了 ValueAnimator.ofInt(0,100) 的靜態工廠方式去構造一個int屬性集合的 ValueAnimator 物件。一般情況下,你也可以通過 new ValueAnimator() 的方式構建一個 ValueAnimator 物件,然後通過 set[Type]Values 方式注入你所需要的值集合。但是相比第一種方法,通過 new 方式的構造手段程式碼偏多而且不集中,也不利於維護(當然凡事沒有絕對,需要考慮你自己的業務場景)。

public static ValueAnimator ofInt(int... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setIntValues(values);
        return anim;
    }

public void setIntValues(int... values) {
        if (values == null || values.length == 0) {
            return;
        }
        if (mValues == null || mValues.length == 0) {
            setValues(PropertyValuesHolder.ofInt("", values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setIntValues(values);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }
複製程式碼

ofInt 函式中, ValueAnimator 將生成一個 ValueAnimator 物件,然後通過呼叫 setIntValues 方法將 values 陣列轉為 PropertyValuesHolder 物件。而 int 陣列型別 valuesPropertyValuesHolder 物件的轉換是通過 PropertyValuesHolderofInt 方法實現:

//code PropertyValuesHolder.java
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
        return new IntPropertyValuesHolder(propertyName, values);
    }
複製程式碼

PropertyValuesHolder 是什麼呢?我們可以通過 PropertyValuesHolder 的註釋看出一些端倪:

/**
 * This class holds information about a property and the values that that property
 * should take on during an animation. PropertyValuesHolder objects can be used to create
 * animations with ValueAnimator or ObjectAnimator that operate on several different properties
 * in parallel.
 */
複製程式碼

大致意思就是一個存放屬性和值的容器,而每次動畫的過程中都會從這個容器中取值或者設定值。PropertyValuesHolderofInt 方法將返回一個 IntPropertyValuesHolder 型別物件。這個型別的作用就像它名字一樣限定了 Holder 中所存放的型別是 Int 型別。Ok,我們說到這裡,我們可以看到一個簡單 ValueAnimator 物件的生成,實際上伴隨著多個型別的物件。

相關類圖

ValueAnimatorsetDuration方法純粹就是記錄一個mDuration時間,沒有特別的操作。

@Override
    public ValueAnimator setDuration(long duration) {
        if (duration < 0) {
            throw new IllegalArgumentException("Animators cannot have negative duration: " +
                    duration);
        }
        mDuration = duration;
        return this;
    }
複製程式碼

我們重點看下start()方法:

//code ValueAnimator.java
@Override
  public void start() {
        start(false);
 }

private void start(boolean playBackwards/*是否有返回操作*/) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        ....
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        mLastFrameTime = 0;
        AnimationHandler animationHandler = AnimationHandler.getInstance();
        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
//加入到animationHandler管理

        if (mStartDelay == 0 || mSeekFraction >= 0) {
            startAnimation();
            if (mSeekFraction == -1) {
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

複製程式碼

ValueAnimatorstart函式中會呼叫內部的start(boolean playBackwards)函式。引數playBackwards代表動畫是否還有回彈操作。可以通過ValueAnimator.reverse()方法將其設定為true

我們剛才說到, ValueAnimator 實現了 AnimationFrameCallback 介面, ValueAnimator.start(boolean) 程式碼中,ValueAnimator先通過一個AnimationHandler.getInstance()方法獲取執行緒內的AnimationHandler單例,然後將實現了AnimationFrameCallbackValueAnimator物件(也就是自己)通過addAnimationFrameCallback方法加入到AnimationHandler物件中去。

//AnimationHandler.java
//sAnimatorHandler是一個ThreadLocal變數,用於儲存AnimationHandler的執行緒單例
public static AnimationHandler getInstance() {
        if (sAnimatorHandler.get() == null) {
            sAnimatorHandler.set(new AnimationHandler());
        }
        return sAnimatorHandler.get();
    }

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);//加入佇列
        }

        if (delay > 0) {
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }
複製程式碼

AnimationHandler類的addAnimationFrameCallback方法中, AnimationHandler將會往getProvider()物件中post一個回撥物件mFrameCallback,而所有的繪製都將通過這個物件進行佇列遍歷來實現。

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);//進行迴圈動畫
            }
        }
    };
複製程式碼

mFrameCallbackChoreographer.FrameCallback的實現類,它實現了doFrame回撥介面,在這個介面中,將通過呼叫doAnimationFrame函式來執行mAnimationCallbacks佇列中的動畫,最後當mAnimationCallbacks佇列中還有物件的時候,將再次執行getProvider().postFrameCallback(this);函式進行迴圈動畫操作。

getProvider()返回一個AnimationFrameCallbackProvider型別的物件。AnimationFrameCallbackProvider是什麼呢?AnimationFrameCallbackProvider其實就是一個跟最終的動畫操作物件**Choreographer**互動的一個介面物件。而在AnimationHandler類中,它的實現類是MyFrameCallbackProvider

//code AnimationHandler.java
private AnimationFrameCallbackProvider getProvider() {
        if (mProvider == null) {
            mProvider = new MyFrameCallbackProvider();
        }
        return mProvider;
    }
複製程式碼

MyFrameCallbackProvider內部實現了跟**Choreographer**物件的操作:

private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

        final Choreographer mChoreographer = Choreographer.getInstance();

        @Override
        public void postFrameCallback(Choreographer.FrameCallback callback) {
            mChoreographer.postFrameCallback(callback);
        }
       ....
    }
複製程式碼

Choreographer物件是什麼呢?如果你做過動畫,或者深入研究過動畫相關,相信對這個類或者這個物件並不陌生,它是android系統中所有動畫和繪製的管理者。簡單概括起來,Choreographer就是一個步調管理者,它是什麼步調呢?就是以16ms左右為頻率做組成的一個繪製訊號。這部分涉及到Android的繪製系統,我們不深入探究,我們可以寫個程式碼簡單瞭解一下:

long time  = SystemClock.uptimeMillis();
        Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long l) {
                Log.d("Choreographer","doFrame use time= "+(SystemClock.uptimeMillis() - time)+"ms");
                time = SystemClock.uptimeMillis();
                Choreographer.getInstance().postFrameCallback(this);
            }
        });
複製程式碼

我們往Choreographer物件中post一個FrameCallback匿名物件,通過變數time來計算每次步調的時差。最後我們可以在日誌中輸出:

11-08 05:31:25.290  9080  9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.308  9080  9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.327  9080  9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.345  9080  9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.363  9080  9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.383  9080  9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.400  9080  9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.421  9080  9080 D Choreographer: doFrame use time= 21ms
11-08 05:31:25.439  9080  9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.457  9080  9080 D Choreographer: doFrame use time= 17ms
11-08 05:31:25.474  9080  9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.494  9080  9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.513  9080  9080 D Choreographer: doFrame use time= 19ms
複製程式碼

可以看出,Choreographer會在一定頻率的步調中執行繪製函式。而這種步調或者是用軟體模擬,或者是通過系統的 VSYNC 訊號實現。我們來整理一下ValueAnimatorstart流程:

ValueAnimator.start呼叫流程圖

通過上面的流程圖我們可以看出,在AnimationHandler進行繪製的時候,實際上是呼叫了ValueAnimatordoAnimationFrame方法:

public final void doAnimationFrame(long frameTime) {
        AnimationHandler handler = AnimationHandler.getInstance();
        if (mLastFrameTime == 0) {
            // First frame
            handler.addOneShotCommitCallback(this);
//對於第一幀新增到commit回撥
            if (mStartDelay > 0) {
                startAnimation();
            }
            if (mSeekFraction < 0) {
                mStartTime = frameTime;
            } else {
                long seekTime = (long) (getScaledDuration() * mSeekFraction);
                mStartTime = frameTime - seekTime;
                mSeekFraction = -1;
            }
            mStartTimeCommitted = false;         }
        mLastFrameTime = frameTime;
        if (mPaused) {
            mPauseTime = frameTime;
            handler.removeCallback(this);
            return;
        } else if (mResumed) {
            mResumed = false;
            if (mPauseTime > 0) {
                mStartTime += (frameTime - mPauseTime);
                mStartTimeCommitted = false; // allow start time to be compensated for jank
            }
            handler.addOneShotCommitCallback(this);
        }
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);//動畫處理函式

        if (finished) {
            endAnimation();
        }
    }
複製程式碼

對於第一幀或者resume後的動畫,將通過handler物件的addOneShotCommitCallback方法將Callback物件加入到Commit佇列中去。之後將呼叫動畫處理函式:animateBasedOnTime(long)

 boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            ....
            animateValue(currentIterationFraction);
        }
        return done;
    }
複製程式碼

animateBasedOnTime函式將呼叫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);
            }
        }
    }
複製程式碼

由於動畫的整個過程相當於是一個以時間為變數的函式: x = f(t)。(t代表時間) 為了方便計算,動畫的計算過程會先將時間變數歸一化,行程進度變數fraction,然後通過差值計算得到相應的差值變數賦值給fraction。 比如:你執行動畫400ms,現在你執行到了200ms,那麼你歸一化以後的進度變數就為200/400 = .5f。如果你採用的是線性差值器的話那麼你的差值變數也同樣為.5fmValues變數指的就是我們上面提到的PropertyValuesHolder變數。PropertyValuesHoldercalculateValue函式,將呼叫mKeyframeSetgetValue函式,而這個函式的引數,就是我們上面

//PropertyValuesHolder.java
void calculateValue(float fraction) {
        mAnimatedValue = mKeyframeSet.getValue(fraction);
    }
複製程式碼

KeyframeSet是什麼呢?我們通過閱讀這個成員的註釋可以看出一些門道:

/**
     * The set of keyframes (time/value pairs) that define this animation.
     */
    KeyframeSet mKeyframeSet = null;
複製程式碼

簡要說明,就是儲存了一些value值,什麼樣的value值呢?用於計算時間和對應值vaue的value集合。我們不妨看下mKeyframeSet是在哪兒被賦值的。由於我們是通過"ValueAnimator.ofInt()"方式來生成一個ValueAnimator物件,因此,ValueAnimator將會通過setIntValues函式給屬性賦值:

public void setIntValues(int... values) {
        mValueType = int.class;
        mKeyframeSet = KeyframeSet.ofInt(values);
//靜態構造KeyframeSet
    }

public static KeyframeSet ofInt(int... values//傳入的是[0,100]) {
        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 {    //step2
            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);
    }
複製程式碼

由於我們此時傳入的陣列是[0,100],所以if語句將跳轉到我們的step2處,之後給每一個值都生成一個Keyframe物件幀放入keyframes陣列集合中,再將陣列集合keyframes存入物件IntKeyframeSet中。Keyframe通過靜態方法ofInt來構建一個Keyframe物件,這個物件第一個浮點引數,代表你這個值在陣列中的偏移。比如你的陣列是[0,1,2,3,4],那麼2在此陣列中的偏移為2 / (5 -1) = 50%。我們再回到PropertyValuesHoldercalculateValue方法,此方法裡呼叫了KeyFrameSetgetValue方法:

//KeyFrameSet.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;
        }
複製程式碼

KeyFrameSetgetValue方法,會通過傳入的差值變數,匹配到 最接近且不超過 的一個時間幀,並通過mEvaluator計算器計算返回給上層呼叫。我們再回朔一下ValueAnimator的繪製過程:

  1. ValueAnimator呼叫start方法將自己放入到AnimatorHandler的佇列中去,AnimatorHandlerpost一個FrameCallbackChoreographer的動畫訊息處理佇列中去。

2.當收到一條VSYNC訊息或者是繪製指令,將回撥ValueAnimatordoAnimationFrame方法,而doAnimationFrame()方法中會呼叫animateBasedOnTime()-> animateValue()方法用於計算。

  1. animateValue()方法計算中會呼叫PropertyValuesHolder[] mValuescalculateValue方法用於計算當前時刻的差值:
void calculateValue(float fraction) {
        mAnimatedValue = mKeyframeSet.getValue(fraction);
    }
複製程式碼

並將當前值儲存在mAnimatedValue變數中去。

我們通過上面的流程解釋了Animator動畫過程中的差值計算,那麼接下去,我們就需要把這個值注入到我們的控制元件屬性中去,這樣才能夠實現動畫的效果。那麼我們計算好了屬性值,我們需要在哪兒注入到我們的控制元件物件中去呢?而且,屬性可能對應的是不同的型別,我們又如何區分不同的型別呢? 我們現在解答第一個問題: 我們看下一下這個例子:

View view = ...;
view.animate().translationX(500).start();
複製程式碼

這時候,我們會看見我們的控制元件view沿著x軸方向正方向平移500個單位。實際上,這種動畫的實現就是用的我們上面的屬性動畫,而屬性動畫的計算過程跟我們上述的一摸一樣。那麼它又是如何將計算好的結果設定到View物件上的呢? 首先,View.animate()方法返回的是一個ViewPropertyAnimator,不要被它的名字所誤導,它並不是一個Animator,它的作用其實類似一個AnimatorBuilder物件

public ViewPropertyAnimator animate() {
        if (mAnimator == null) {
            mAnimator = new ViewPropertyAnimator(this);
        }
        return mAnimator;
    }
複製程式碼

ViewPropertyAnimator呼叫startstartAnimation方法的時候,ViewPropertyAnimator會真正的構造我們的屬性動畫ValueAnimator

//code ViewPropertyAnimator.java
public void start() {
        ...
        startAnimation();
    }

private void startAnimation() {
        ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
        ....
        animator.addUpdateListener(mAnimatorEventListener);//增加回撥
        animator.addListener(mAnimatorEventListener);//增加回撥
        ...
        animator.start();
    }
複製程式碼

這裡,ViewPropertyAnimator會給生成的ValueAnimator物件增加非常重要的介面mAnimatorEventListener。我們知道,ValueAnimator在計算完每一幀以後,都會回撥AnimatorUpdateListener介面的onAnimationUpdate方法:

//code ValueAnimator.java
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);//回撥介面
            }
        }
    }
複製程式碼

也就是說,每次計算完後,ValueAnimator都會回撥mAnimatorEventListener物件的onAnimationUpdate方法,而在mAnimatorEventListener物件的實現中,將會把計算好的值賦予View物件:

//code AnimatorEventListener.java
@Override
        public void onAnimationUpdate(ValueAnimator animation) {
            PropertyBundle propertyBundle = mAnimatorMap.get(animation);
            if (propertyBundle == null) {
                // Shouldn't happen, but just to play it safe
                return;
            }

            boolean hardwareAccelerated = mView.isHardwareAccelerated();
            boolean alphaHandled = false;
            if (!hardwareAccelerated) {
                mView.invalidateParentCaches();
            }
            float fraction = animation.getAnimatedFraction();
            int propertyMask = propertyBundle.mPropertyMask;
            if ((propertyMask & TRANSFORM_MASK) != 0) {
                mView.invalidateViewProperty(hardwareAccelerated, false);
            }
            ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
            if (valueList != null) {
                int count = valueList.size();
                for (int i = 0; i < count; ++i) {
                    NameValuesHolder values = valueList.get(i);
                    float value = values.mFromValue + fraction * values.mDeltaValue;
                    if (values.mNameConstant == ALPHA) {
                        alphaHandled = mView.setAlphaNoInvalidation(value);
                    } else {
                        setValue(values.mNameConstant, value);//設定值
                    }
                }
            }
          ....
        }
複製程式碼

這裡主要呼叫了個叫setValue(values.mNameConstant, value);的方法,而此方法會通過傳入的名字常量執行不同的操作:

private void setValue(int propertyConstant, float value) {
        final View.TransformationInfo info = mView.mTransformationInfo;
        final RenderNode renderNode = mView.mRenderNode;
        switch (propertyConstant) {
            case TRANSLATION_X:
                renderNode.setTranslationX(value);
                break;
            case TRANSLATION_Y:
                renderNode.setTranslationY(value);
                break;
            case TRANSLATION_Z:
                renderNode.setTranslationZ(value);
                break;
            case ROTATION:
                renderNode.setRotation(value);
                break;
            case ROTATION_X:
                renderNode.setRotationX(value);
                break;
            case ROTATION_Y:
                renderNode.setRotationY(value);
                break;
              ....
        }
    }
複製程式碼

相關文章