Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android源碼分析系列(二):propertyAnimation和NineOldAndroids源碼分析

android源碼分析系列(二):propertyAnimation和NineOldAndroids源碼分析

編輯:關於Android編程

PropertyAnimation

由於nineoldandroids原理上和官方的實現是一樣的,並且他還支持API11以下的版本,所以我們的源碼學習以noa為主

問題

view的animation為什麼不能真正的改變view的有效區域,而只是改變了view的繪制? property anim和view anim的實現有啥區別,為啥可以真正改變? TypeEvaluator的第一個參數的由來?(可以通過ObjectAnimator來分析)

源碼分析

初始化分析

即我們傳進去的值都變成了什麼。

先看一下基本的用法

ValueAnimator colorAnim = ObjectAnimator.ofFloat(myView, "scaleX", 0.3f); 
colorAnim.setDuration(1000); 
colorAnim.start();

這是我們的一般用法。也是我們的入口。我們從入口開始看起。也就是這個方法

 public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

可以看到他是利用類中的private構造函數生成了一個ObjectAnimator對象

 private ObjectAnimator(Object target, String propertyName) {
        mTarget = target;
        setPropertyName(propertyName);
    }

設置target對象,設置屬性名稱。我們來看看這個setPropertyName和setFloatValue方法

 public void setPropertyName(String propertyName) {
        // mValues could be null if this is being constructed piecemeal. Just record the
        // propertyName to be used later when setValues() is called if so.
        if (mValues != null) {
            PropertyValuesHolder valuesHolder = mValues[0];
            String oldName = valuesHolder.getPropertyName();
            valuesHolder.setPropertyName(propertyName);
            mValuesMap.remove(oldName);
            mValuesMap.put(propertyName, valuesHolder);
        }
       // 因為此時mValues為空,所以直接執行這裡
        mPropertyName = propertyName;
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }
// 設置屬性值
public void setFloatValues(float... values) {
        // 由於之前的mValues依然為空,所以我們執行了這個分支
        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.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }

        } else {
            super.setFloatValues(values);
        }
    }

可以看到官方介紹中傳入的這個屬性名是為了獲得一個對於該屬性名的setter函數
對於setPropertyName方法,我們可以看到這裡有好幾個成員變量 mValues,mValuesMap都是定義在ValueAnimator中,這個判斷的意義在於更換propertyName時有用,一般我們直接用的話就是直接跳過然後為mPropertyName賦值。

我們先來看看PropertyValuesHolder.ofFloat()方法

  public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
    }

 public FloatPropertyValuesHolder(String propertyName, float... values) {
            super(propertyName);
            setFloatValues(values);
        }

所以我們最終還是進入了setFloatValues中。

 public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframeSet = KeyframeSet.ofFloat(values);
    }

這裡注意一下這兩個的區別,mValues和values,mValues是我們之前傳入的屬性名,每一個對象可以有很多需要動畫的屬性名,然後他們存在mValues中,values是這個屬性的一系列取值。我們繼續看PropertyValuesHolder.setFloatValues()這個方法

 public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframeSet = KeyframeSet.ofFloat(values);
    }

這裡引出了KeyframeSet這個類,KeyFrameSet保存了一系列的keyframe值。我們來看看他是怎麼樣處理我們傳入的不同值的。

 public static KeyframeSet ofFloat(float... values) {
        int numKeyframes = values.length;
       //keyframe值至少都要是2,當我們傳入1個參數時默認為end值,他會在內部生成兩個KeyFrame
        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
            keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
        } else {
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                keyframes[i] = (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
            }
        }
        return new FloatKeyframeSet(keyframes);
    }

貌似,當我們傳入多個值時,它會平均分!比如我們輸入 1,2,3,4那麼,一共分成了三段,2就是在(1/3,2),我感覺這個第一個值一定是時間流逝比,或者是插值分數。繼續看KeyFrame.ofFloat()方法

 /**
     * Constructs a Keyframe object with the given time and value. The time defines the
     * time, as a proportion of an overall animation's duration, at which the value will hold true
     * for the animation. The value for the animation between keyframes will be calculated as
     * an interpolation between the values at those keyframes.
     *
     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
     * of time elapsed of the overall animation duration.
     * @param value The value that the object will animate to as the animation time approaches
     * the time in this keyframe, and the the value animated from as the time passes the time in
     * this keyframe.
     */
    public static Keyframe ofFloat(float fraction, float value) {
        return new FloatKeyframe(fraction, value);
    }

通過官方介紹我們可以看到,fraction確實是時間的流逝比。

到此為止,我們就獲得了mKeyframeSet。並且他是一個FloatKeyFrameSet。
這樣的話,ValueAnimator就持有了一個PropertyValuesHolder的HashMap,並且一個PropertyValuesHolder又有一個KeyframeSet對象。

當我們想獲得任意時刻的propertyValuesHolder的值怎麼辦?我們可以調用KeyframeSet的getValue方法,以下是FloatKeyframeSet,跟其也差不多。

// 我們傳入的參數應該也是一個時間流逝比
 public float getFloatValue(float fraction) {
       // 如果我們只傳入了一個參數
        if (mNumKeyframes == 2) {
            if (firstTime) {
                firstTime = false;
                firstValue = ((FloatKeyframe) mKeyframes.get(0)).getFloatValue();
                lastValue = ((FloatKeyframe) mKeyframes.get(1)).getFloatValue();
                deltaValue = lastValue - firstValue;
            }
         // 從這裡看出來我們是可以給每一個KeyFrameSet設置單獨的interpolator的,並且它會返回我們的時間流逝比得到的插值分數
            if (mInterpolator != null) {
                fraction = mInterpolator.getInterpolation(fraction);
            }
           // 可見這是默認的設置,就是通過插值分數求得具體的值
            if (mEvaluator == null) {
                return firstValue + fraction * deltaValue;
            } else {
                return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).floatValue();
            }
        }
        if (fraction <= 0f) {    // 時間流逝比小於0時,取第一幀和第二幀並且以第二幀來處理
            final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
            final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
            float prevValue = prevKeyframe.getFloatValue();
            float nextValue = nextKeyframe.getFloatValue();
            float prevFraction = prevKeyframe.getFraction();
            float nextFraction = nextKeyframe.getFraction();
            final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
            return mEvaluator == null ?
                    prevValue + intervalFraction * (nextValue - prevValue) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                            floatValue();
        } else if (fraction >= 1f) {   // 時間流逝比大於1時,取最後兩幀,去最後一幀的插值器
            final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
            final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
            float prevValue = prevKeyframe.getFloatValue();
            float nextValue = nextKeyframe.getFloatValue();
            float prevFraction = prevKeyframe.getFraction();
            float nextFraction = nextKeyframe.getFraction();
            final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
            return mEvaluator == null ?
                    prevValue + intervalFraction * (nextValue - prevValue) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                            floatValue();
        }
        FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
        for (int i = 1; i < mNumKeyframes; ++i) {
            FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
            if (fraction < nextKeyframe.getFraction()) {
                final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
                if (interpolator != null) {
                    fraction = interpolator.getInterpolation(fraction);
                }
                float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                    (nextKeyframe.getFraction() - prevKeyframe.getFraction());
                float prevValue = prevKeyframe.getFloatValue();
                float nextValue = nextKeyframe.getFloatValue();
                return mEvaluator == null ?
                        prevValue + intervalFraction * (nextValue - prevValue) :
                        ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                            floatValue();
            }
            prevKeyframe = nextKeyframe;
        }
        // shouldn't get here
        return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
    }

可以看到這裡一共處理了4種情況:
mKeyframeNums == 2
fraction <= 0
fraction >= 1
fraction在(0,1)之間且mKeyframeNums != 2

並且很明顯的看到我們傳入的時間流逝比,通過插值器變成了插值分數,並且將插值分數傳入了Evaluator中。
並且根據我們傳入的時間流逝比的不同,也有不同的處理方法:
- 傳入value為1即KeyframeSet為2,返回由插值器計算得的值
- fraction<= 0 或fraction>=1,選擇固定的兩幀。
- fraction位於0和1之間且KeyframeSet不為2時,取第一幀和大於該fraction的那一個fraction

總結一下,當我們在傳入多個values,他們會成為PropertyValuesHolder下的一系列KeyframeSet,然後會根據我們傳入時間流逝比值的不同獲取到對應的具體值。

我們再轉回來,回到setValues方法中。

 /**
     * Sets the values, per property, being animated between. This function is called internally
     * by the constructors of ValueAnimator that take a list of values. But an ValueAnimator can
     * be constructed without values and this method can be called to set the values manually
     * instead.
     *
     * @param values The set of values, per property, being animated between.
     */
    public void setValues(PropertyValuesHolder... values) {
        int numValues = values.length;
        mValues = values;
        mValuesMap = new HashMap(numValues);
        for (int i = 0; i < numValues; ++i) {
            PropertyValuesHolder valuesHolder = (PropertyValuesHolder) values[i];
            mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

從這裡我們看到,mValues終於被初始化了。所有的參數都已經准備好,大刀已經饑渴難耐了。我們接下來分析動畫的整個調用流程。

調用分析

執行動畫要調用ValueAnimator.start方法。

   /**
     * Start the animation playing. This version of start() takes a boolean flag that indicates
     * whether the animation should play in reverse. The flag is usually false, but may be set
     * to true if called from the reverse() method.
     *
     * 

The animation started by calling this method will be run on the thread that called * this method. This thread should have a Looper on it (a runtime exception will be thrown if * this is not the case). Also, if the animation will animate * properties of objects in the view hierarchy, then the calling thread should be the UI * thread for that view hierarchy.

* * @param playBackwards Whether the ValueAnimator should start playing in reverse. */ 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; sPendingAnimations.get().add(this); if (mStartDelay == 0) { // This sets the initial value of the animation, prior to actually starting it running setCurrentPlayTime(getCurrentPlayTime()); mPlayingState = STOPPED; mRunning = true; // 如果有listener的話就把事件回調出去,並且這個事件要在動畫真正的開始之前實現 if (mListeners != null) { ArrayList tmpListeners = (ArrayList) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationStart(this); } } } AnimationHandler animationHandler = sAnimationHandler.get(); if (animationHandler == null) { animationHandler = new AnimationHandler(); sAnimationHandler.set(animationHandler); } animationHandler.sendEmptyMessage(ANIMATION_START); }

然後看到當馬上執行時,我們執行了setCurrentPlayTime()方法

  /**
     * 設置動畫的指定起始時間點,如果動畫沒開始,只有設置了該時間點以後,動畫才會開始執行。
     * 如果動畫已經在運行了,那麼會為動畫的時間重新賦值,並從該點繼續執行,
     * 參數:動畫起始點單位,單位:毫秒
     *
     * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
     */
    public void setCurrentPlayTime(long playTime) {
        initAnimation();
        long currentTime = AnimationUtils.currentAnimationTimeMillis();
        if (mPlayingState != RUNNING) {
            mSeekTime = playTime;
            mPlayingState = SEEKED;
        }
        mStartTime = currentTime - playTime;
        animationFrame(currentTime);
    }

我們之前對其的參數傳入為0,然後就是initAnimation和animationFrame方法

// initAnimation方法主要是把每一個propertyValuesHolder調用init方法,其中init方法就是判斷數據類型選擇Evaluator,然後把這個Evaluator
//設置給KeyframeSet
 void initAnimation() {
        if (!mInitialized) {
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].init();
            }
            mInitialized = true;
        }
    }

// 這個方法中包含了給定動畫的一幀,他的參數就是當前的時間,用來計算時間的流逝比,並且每一幀都會調用這個方法
  boolean animationFrame(long currentTime) {
        boolean done = false;
        if (mPlayingState == STOPPED) {
            mPlayingState = RUNNING;
            if (mSeekTime < 0) {
                mStartTime = currentTime;
            } else {
                mStartTime = currentTime - mSeekTime;
                // Now that we're playing, reset the seek time
                mSeekTime = -1;
            }
        }
        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;
    }

然後,在計算了時間流逝比之後,就會調用這個方法

 /**
     * 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 end()
     * function is called, to set the final value on the property.
     *
     * 

Overrides of this method must call the superclass to perform the calculation * of the animated value.

* * @param fraction The elapsed fraction of the animation. */ 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時必須要自己手動給他設置一樣 } } } //上面是ValueAnimator的animateValues方法,接下來看ObjectAnimator的animateValues方法 @Override void animateValue(float fraction) { super.animateValue(fraction); int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].setAnimatedValue(mTarget); } } // 返回我們上面分析的getValues方法的實際值 void calculateValue(float fraction) { mAnimatedValue = mKeyframeSet.getValue(fraction); }

由於所有的都是通過Keyframe來實現的。我們來掃描一下Keyframe的定義
持有動畫time/value的鍵值對。KeyFrame 累用於定義目標動畫對象過程中的屬性值。由於時間是在一幀到另一幀之間變化,所有目標對象的動畫值將會在前一幀值和後一幀值之間。每一幀持有了可選的TimeInterpolator對象,為每一幀單獨設置Interpolator.Keyframe本身是抽象類,指定類型的工廠函數將會根據保存的值類型返回一明確的Keyfame對象。系統對float和int類型的值,有性能的優化。除非你需要處理一個自定義的類型或者要求直接應用動畫的數據結構(或者實現TypeEvaluator)之外,你應該使用int或者float類型的值。

接下來就是關鍵了。這個是ObjectAnimator進行更改自己的屬性的操作。

/**
     * Internal function to set the value on the target object, using the setter set up
     * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
     * to handle turning the value calculated by ValueAnimator into a value set on the object
     * according to the name of the property.
     * @param target The target object on which the value is set
     */
    void setAnimatedValue(Object target) {
        if (mProperty != null) {
            // 將我們要變化的目標對象更改為我們通過三步計算獲取到的值
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }
   // 這個mAnimatedValue就是我們之前調用KeyframeSet的getValues方法獲得的當前時刻下該動畫屬性的值
 Object getAnimatedValue() {
        return mAnimatedValue;
    }

可以看到,這裡將我們之前的計算值用上了。並且通過反射機制去修改屬性值。所以,這也是為什麼屬性必須有setter方法的原因,因為不這樣,我們沒有辦法去修改這個值

從而,setCurrentPlayTime就結束了。總結一下就是,我們通過不斷的將當前的時間傳進去,獲取到fraction,然後根據這個值進行二步運算後得到屬性的實際值,最後通過反射的方法來改變這個屬性值。

但是,這個過程僅僅是動畫開始的時候進行的第一步操作,他把動畫的整個流程都走了一遍。剩下的動畫就需要handler來管理了。

下面分析handler的操作。

        AnimationHandler animationHandler = sAnimationHandler.get();
        if (animationHandler == null) {
            animationHandler = new AnimationHandler();
            sAnimationHandler.set(animationHandler);
        }
        animationHandler.sendEmptyMessage(ANIMATION_START);
    }

這裡的sAnimationHandler是一個ThreadLocal,這樣就保證了線程安全。
下面看看handleMessage方法

private static class AnimationHandler extends Handler {
    /**
     * 有兩種消息我們需要關心:ANIMATION_START 和 ANIMATION_FRAME
     * START消息:當start()方法被調用的時候,會發送一個START消息。當start()方法被調用的時候,動畫的啟動並不是同步的。
     * 因為它可能是在錯誤的線程中被調用,並且由於每個動畫的時序是不同的,所以也不可能與其它的動畫同步。
     * 每一個動畫向Handler發送START消息的時候,就會觸發Handler把該動畫放到處於活動的動畫隊列中去,並開始
     * 該動畫的frame。
     * FRAME消息:只要有活動狀態的動畫需要去處理,就會一直發送。
     */
    @Override
    public void handleMessage(Message msg) {
        boolean callAgain = true;
        ArrayList animations = sAnimations.get(); //准備開始執行的動畫 
        ArrayList delayedAnims = sDelayedAnims.get();//需要延遲執行的動畫
        switch (msg.what) {
            // TODO: should we avoid sending frame message when starting if we
            // were already running?
            case ANIMATION_START:
                ArrayList pendingAnimations = sPendingAnimations.get();
                if (animations.size() > 0 || delayedAnims.size() > 0) {
                    callAgain = false;
                }
                // pendingAnims holds any animations that have requested to be started
                // We're going to clear sPendingAnimations, but starting animation may
                // cause more to be added to the pending list (for example, if one animation
                // starting triggers another starting). So we loop until sPendingAnimations
                // is empty.
                while (pendingAnimations.size() > 0) {
                    ArrayList pendingCopy =
                            (ArrayList) pendingAnimations.clone();
                    pendingAnimations.clear();
                    int count = pendingCopy.size();
                    for (int i = 0; i < count; ++i) {
                        ValueAnimator anim = pendingCopy.get(i);
                        // If the animation has a startDelay, place it on the delayed list
                        if (anim.mStartDelay == 0) {
                            anim.startAnimation();
                        } else {
                            delayedAnims.add(anim);
                        }
                    }
                }
            // 注意,這裡沒有break,ANIMATION_FRAME中代碼將繼續執行 
            case ANIMATION_FRAME:
                // 在當前幀內,currentTime對於所有的動畫處理,持有相同的時間
                long currentTime = AnimationUtils.currentAnimationTimeMillis();
                ArrayList readyAnims = sReadyAnims.get();
                ArrayList endingAnims = sEndingAnims.get();

                int numDelayedAnims = delayedAnims.size();//如果延遲隊列中的動畫到了該啟動的時候,那麼它們加入到活動動畫隊列中去
                for (int i = 0; i < numDelayedAnims; ++i) {
                    ValueAnimator anim = delayedAnims.get(i);
                    if (anim.delayedAnimationFrame(currentTime)) {
                        readyAnims.add(anim);
                    }
                }
                int numReadyAnims = readyAnims.size();
                if (numReadyAnims > 0) {
                    for (int i = 0; i < numReadyAnims; ++i) {
                        ValueAnimator anim = readyAnims.get(i);
                        anim.startAnimation();//該方法將將要執行的動畫加入到活動動畫隊列中,這裡其實並沒有刷新屬性值的方法
                        anim.mRunning = true;
                        delayedAnims.remove(anim);//動畫開始,將其從延遲動畫中干掉
                    }
                    readyAnims.clear();
                }

                // 處理所有活動中的動畫,animationFrame()的返回值決定該當動畫是否執行完畢
                int numAnims = animations.size();
                int i = 0;
                while (i < numAnims) {
                    ValueAnimator anim = animations.get(i);
                    if (anim.animationFrame(currentTime)) {//關鍵代碼,循環的去調用animationFrame方法,去進行一些列的計算,並更新屬性值
                        endingAnims.add(anim);//如果該動畫執行完畢,則加入到結束隊列中去
                    }
                    if (animations.size() == numAnims) {//動畫沒被取消,++i
                        ++i;
                    } else {//當前正在執行的動畫被cancle的情況 
                        // An animation might be canceled or ended by client code
                        // during the animation frame. Check to see if this happened by
                        // seeing whether the current index is the same as it was before
                        // calling animationFrame(). Another approach would be to copy
                        // animations to a temporary list and process that list instead,
                        // but that entails garbage and processing overhead that would
                        // be nice to avoid.
                        --numAnims;
                        endingAnims.remove(anim);
                    }
                }
                if (endingAnims.size() > 0) {
                    for (i = 0; i < endingAnims.size(); ++i) {
                        endingAnims.get(i).endAnimation();
                    }
                    endingAnims.clear();
                }

                if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) {//如果有活動或者延遲狀態的動畫,那麼就繼續發送FRAME消息,循環
                    sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -
                            (AnimationUtils.currentAnimationTimeMillis() - currentTime)));
                }
                break;
        }
    }
}

這裡的思路就是循環的去發送ANIMATION_FRAME消息,讓這個過程不斷進行,如果有活動,或者延遲狀態的動畫,就一直進行下去。
而處理正在進行的動畫的方法,就是不斷的去調用animateFrame這個方法,它會不斷的去進行一些列的計算,並且為動畫的屬性賦予新值。


NineOldAndroids原理

從上面我們handleMessage的實現我們來看,對於沒有延遲的動畫,他調用了一個startAnimation方法。

private void startAnimation() {
        initAnimation();
        sAnimations.get().add(this);
        if (mStartDelay > 0 && mListeners != null) {
            // Listeners were already notified in start() if startDelay is 0; this is
            // just for delayed animations
            ArrayList tmpListeners =
                    (ArrayList) mListeners.clone();
            int numListeners = tmpListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                tmpListeners.get(i).onAnimationStart(this);
            }
        }
    }

// 重點是這個initAnimation方法
void initAnimation() {
        if (!mInitialized) {
            // mValueType may change due to setter/getter setup; do this before calling super.init(),
            // which uses mValueType to set up the default type evaluator.
            if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
                setProperty(PROXY_PROPERTIES.get(mPropertyName));
            }
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].setupSetterAndGetter(mTarget);
            }
            super.initAnimation();
        }
    }

注意到這裡有個很長的判斷 AnimatorProxy.NEEDS_PROXY,這個就是用來系統版本是否小於API11的。

public static final boolean NEEDS_PROXY = Integer.valueOf(Build.VERSION.SDK).intValue() < Build.VERSION_CODES.HONEYCOMB;

PROXY_PROPERTIES是個啥?

 private static final Map PROXY_PROPERTIES = new HashMap();

    static {
        PROXY_PROPERTIES.put("alpha", PreHoneycombCompat.ALPHA);
        PROXY_PROPERTIES.put("pivotX", PreHoneycombCompat.PIVOT_X);
        PROXY_PROPERTIES.put("pivotY", PreHoneycombCompat.PIVOT_Y);
        PROXY_PROPERTIES.put("translationX", PreHoneycombCompat.TRANSLATION_X);
        PROXY_PROPERTIES.put("translationY", PreHoneycombCompat.TRANSLATION_Y);
        PROXY_PROPERTIES.put("rotation", PreHoneycombCompat.ROTATION);
        PROXY_PROPERTIES.put("rotationX", PreHoneycombCompat.ROTATION_X);
        PROXY_PROPERTIES.put("rotationY", PreHoneycombCompat.ROTATION_Y);
        PROXY_PROPERTIES.put("scaleX", PreHoneycombCompat.SCALE_X);
        PROXY_PROPERTIES.put("scaleY", PreHoneycombCompat.SCALE_Y);
        PROXY_PROPERTIES.put("scrollX", PreHoneycombCompat.SCROLL_X);
        PROXY_PROPERTIES.put("scrollY", PreHoneycombCompat.SCROLL_Y);
        PROXY_PROPERTIES.put("x", PreHoneycombCompat.X);
        PROXY_PROPERTIES.put("y", PreHoneycombCompat.Y);
    }

所以假設我們這裡是一個scaleX,那麼他就返回了一個PreHoneycombCompat.SCALE_X。繼續看這玩意

 static Property SCALE_X = new FloatProperty("scaleX") {
        @Override
        public void setValue(View object, float value) {
            AnimatorProxy.wrap(object).setScaleX(value);
        }
        @Override
        public Float get(View object) {
            return AnimatorProxy.wrap(object).getScaleX();
        }
    };

原來他被封裝成了一個Property。然後在property.setValues時調用setScaleX方法。重點就是這個AnimatorProxy,他就是這個兼容性的體現所在。他是通過矩陣實現了這些變換效果。

public final class AnimatorProxy extends Animation {
    /** Whether or not the current running platform needs to be proxied. */
    public static final boolean NEEDS_PROXY = Integer.valueOf(Build.VERSION.SDK).intValue() < Build.VERSION_CODES.HONEYCOMB;

    private static final WeakHashMap PROXIES =
            new WeakHashMap();

    public static AnimatorProxy wrap(View view) {
        AnimatorProxy proxy = PROXIES.get(view);
        // This checks if the proxy already exists and whether it still is the animation of the given view
        if (proxy == null || proxy != view.getAnimation()) {
            proxy = new AnimatorProxy(view);
            PROXIES.put(view, proxy);
        }
        return proxy;
    }

    private final WeakReference mView;
    private final Camera mCamera = new Camera();
    private boolean mHasPivot;

    private float mAlpha = 1;
    private float mPivotX;
    private float mPivotY;
    private float mRotationX;
    private float mRotationY;
    private float mRotationZ;
    private float mScaleX = 1;
    private float mScaleY = 1;
    private float mTranslationX;
    private float mTranslationY;

    private final RectF mBefore = new RectF();
    private final RectF mAfter = new RectF();
    private final Matrix mTempMatrix = new Matrix();

    private AnimatorProxy(View view) {
        setDuration(0); //perform transformation immediately
        setFillAfter(true); //persist transformation beyond duration
        view.setAnimation(this);
        mView = new WeakReference(view);
    }


public void setScaleX(float scaleX) {
        if (mScaleX != scaleX) {
            prepareForUpdate();
            mScaleX = scaleX;
            invalidateAfterUpdate();
        }
    }
// 在set屬性值的時候先後調用了下面兩個辦法
 private void prepareForUpdate() {
        View view = mView.get();
        if (view != null) {
            computeRect(mBefore, view);
        }
    }

 private void invalidateAfterUpdate() {
        View view = mView.get();
        if (view == null || view.getParent() == null) {
            return;
        }

        final RectF after = mAfter;
        computeRect(after, view);
        after.union(mBefore);

        ((View)view.getParent()).invalidate(
                (int) Math.floor(after.left),
                (int) Math.floor(after.top),
                (int) Math.ceil(after.right),
                (int) Math.ceil(after.bottom));
    }

private void computeRect(final RectF r, View view) {
        // compute current rectangle according to matrix transformation
        final float w = view.getWidth();
        final float h = view.getHeight();

        // use a rectangle at 0,0 to make sure we don't run into issues with scaling
        r.set(0, 0, w, h);

        final Matrix m = mTempMatrix;
        m.reset();
        transformMatrix(m, view);
        mTempMatrix.mapRect(r);

        r.offset(view.getLeft(), view.getTop());

        // Straighten coords if rotations flipped them
        if (r.right < r.left) {
            final float f = r.right;
            r.right = r.left;
            r.left = f;
        }
        if (r.bottom < r.top) {
            final float f = r.top;
            r.top = r.bottom;
            r.bottom = f;
        }
    }

最後是通過矩陣來進行的變換,到了這裡我就進行不下去了。實在不知道矩陣到底怎麼用的。打算明天學學矩陣。


最終總結,nineoldandroids的方法就是在執行動畫的時候進行判斷,如果版本小於11,就會用矩陣變換的方法來變換view的屬性,否則就像系統一樣會使用反射的方法來變換view的屬性。

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