Android動畫整理和屬性動畫源碼分析

一、基礎

1.動畫總結

2.補間動畫總結

3.幀動畫總結

4.屬性動畫總結

屬性動畫 Property Animation(上手篇)

屬性動畫 Property Animation(進階篇)

二、屬性動畫源碼分析

以 ObjectAnimator 爲例來寫一個簡單的右移動畫

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(iv,"translationX",0,100).setDuration(1 * 1000);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

1.創建動畫

從 ObjectAnimator.ofFloat()開始

    /**
     * 構建一個返回值爲 float 的 ObjectAnimator 的實例
     * @param target 作用於動畫的對象。
     * @param propertyName 屬性名稱,要求對象須有setXXX() 方法,且是 public 的。
     * @param values,屬性變化的值
     */
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

ofFloat()中首先創建一個 ObjectAnimator 的對象,然後設置值。看一下ObjectAnimator的構造方法:

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

分別調用了setTargetsetPropertyName方法,再看一下setTarget

    @Override
    public void setTarget(@Nullable Object target) {
        final Object oldTarget = getTarget();
        if (oldTarget != target) {
            if (isStarted()) {
                cancel();
            }
            mTarget = target == null ? null : new WeakReference<Object>(target);
            // 開始動畫前需要重新初始化target
            mInitialized = false;
        }
    }

先判斷目標對象是否相同,不相同則判斷是否動畫在執行,執行就取消動畫,將目標對象用弱引用保存。再看下setPropertyName

    public void setPropertyName(@NonNull 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);
        }
        mPropertyName = propertyName;
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

大體是保存propertyName,並且給PropertyValuesHolder設置propertyName並存入map中。對於屬性動畫來說,其屬性相關的變量都被封裝在了 PropertyValuesHolder 裏。

再回到ofFloat中,接下來執行了ObjectAnimatorsetFloatValues方法:

    @Override
    public void setFloatValues(float... 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.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }

mValues沒有值的時候調用父類的setFloatValues,父類是ValueAnimatorsetFloatValues如下:

    public void setFloatValues(float... values) {
        if (values == null || values.length == 0) {
            return;
        }
        if (mValues == null || mValues.length == 0) {
            setValues(PropertyValuesHolder.ofFloat("", values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setFloatValues(values);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

可以看出,不管是否調用父類的 setFloatValues()。最後都是要將 values 逐個構造成 PropertyValuesHolder,最後存放在前面所說的 HashMap 裏面。繼續來看看 PropertyValuesHolder#ofFloat() 方法:

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

創建 FloatPropertyValuesHolder對象,相應的還有 IntPropertyValuesHolderMultiIntValuesHolder以及MultiFloatValuesHolder,都是 PropertyValuesHolder 的子類。

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

調用父類的構造方法並傳遞了 propertyName,調用 setFloatValues方法的,其又進一步調用了父類的 setFloatValues,在父類的 setFloatValues方法裏初始化了動畫的關鍵幀。

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

調用KeyframeSet.ofFloat(values)方法,這個方法是將最初傳進過來的可變參數,進行重新的封裝,並且分爲了兩種情況,如果可變參數只傳遞了一個參數,那麼就默認添加一個初始關鍵幀,並將傳入的那個參數設置最後一個關鍵幀。如果已經傳遞了多個參數,那麼就將各個參數設置成關鍵幀。

然後回到一開始的代碼,調用setDuration

    @Override
    @NonNull
    public ObjectAnimator setDuration(long duration) {
        super.setDuration(duration);
        return this;
    }

內部調用了父類的方法,存儲下 duration 的值:

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

然後執行到objectAnimator.setInterpolator(new LinearInterpolator());,看下源碼:

@Override
    public void setInterpolator(TimeInterpolator value) {
        if (value != null) {
            mInterpolator = value;
        } else {
            mInterpolator = new LinearInterpolator();
        }
    }

如果傳遞的是 null 的話,則默認使用的便是 LinearInterpolator,即線性插值器。插值器集成自TimeInterpolator接口,來看下代碼:

/**
 * 插值器定義了動畫變化的頻率,其可以是線性的也可以是非線性的,如加速運動或者減速運動。
 */
public interface TimeInterpolator {

    /**
     * 這裏傳進來的 input 代表當前時間與總時間的比,根據這個時間佔比返回當前的變化頻率。其輸出與輸值都在 [0,1] 之間。
     */
    float getInterpolation(float input);
}

來看看 LinearInterpolatorgetInterpolation實現:

    public float getInterpolation(float input) {
        return input;
    }

對,就是返回原值,因爲時間的變化肯定始終都是勻速的。

到此動畫已經創建完畢,下一步就是執行動畫了。

2.執行動畫

執行動畫從start方法開始,具體代碼:

    @Override
    public void start() {
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        if (DBG) {
            Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                Log.d(LOG_TAG, "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                    pvh.mKeyframes.getValue(1));
            }
        }
        super.start();
    }

最終調用了父類方法:

    @Override
    public void start() {
        start(false);
    }


    private void start(boolean playBackwards) {
    //需要在有looper的線程執行
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
	...
        addAnimationCallback(0);

        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            startAnimation();
            if (mSeekFraction == -1) {
                // No seek, start at play time 0. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

看下addAnimationCallback方法:

	//1.
    private void addAnimationCallback(long delay) {
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }

/**
     * Register to get a callback on the next frame after the delay.
     */
     //2.
    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));
        }
    }

主要是向 AnimationHander 添加一個回調接口AnimationHandler.AnimationFrameCallback,而ValueAnimator 就實現了 AnimationFrameCallback,傳遞的this,具體實現的方法後面會用到,後面再說。

接下來回到start方法,看下startAnimation方法,

	//在內部調用,通過將動畫添加到動畫列表來啓動動畫。必須在UI線程上調用。
    private void startAnimation() {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                    System.identityHashCode(this));
        }

        mAnimationEndRequested = false;
        initAnimation();
        mRunning = true;
        if (mSeekFraction >= 0) {
            mOverallFraction = mSeekFraction;
        } else {
            mOverallFraction = 0f;
        }
        if (mListeners != null) {
            notifyStartListeners();
        }
    }

看到內部調用了initAnimation

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

mValuesPropertyValuesHolder 數組,這裏的目的是初始化 PropertyValuesHolder,再看下init方法:

    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
            mKeyframes.setEvaluator(mEvaluator);
        }
    }

init方法的主要目的是就是給關鍵幀設置估值器。因爲我們前面調用的是 ObjectAnimator#ofFloat() 方法,所以這裏默認給的就是 FloatEvaluator。這裏也來分析一下估值器。

估值器繼承自TypeEvaluator接口:

public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}

fraction 代表了startValueendValue 之間的比例,startValueendValue 是我們自己在 ofFloat() 調用時設定的那個。返回結果就是一個線性的結果,在 startValueendValue 之間。那麼來看看 FloatEvaluator 的實現:

public class FloatEvaluator implements TypeEvaluator<Number> {
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

很簡單,就是起點值加上當前的段值。

再回到最初的 start方法裏,經 startAnimation設置了 KeyFrame 的估值器後,接下來就會進一步調用 setCurrentPlayTime 來開始動畫:

    public void setCurrentPlayTime(long playTime) {
        float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
        setCurrentFraction(fraction);
    }

初始時調用的是setCurrentPlayTime(0),也就是 playTime 爲 0,而 mDuration 是我們自己通過 setDuration() 來設置的。所以這裏得到的 fraction 也是 0。進一步看 setCurrentFraction() :

public void setCurrentFraction(float fraction) {
        // 再次調用 initAnimation()
        initAnimation();
        // 校準 fraction 爲 [0, mRepeatCount + 1]
        fraction = clampFraction(fraction);
        mStartTimeCommitted = true; // do not allow start time to be compensated for jank
        if (isPulsingInternal()) {
            long seekTime = (long) (getScaledDuration() * fraction);
            // 獲取動畫的當前運行時間
            long currentTime = AnimationUtils.currentAnimationTimeMillis();
            // Only modify the start time when the animation is running. Seek fraction will ensure
            // non-running animations skip to the correct start time.
            // 得到開始時間
            mStartTime = currentTime - seekTime;
        } else {
            // If the animation loop hasn't started, or during start delay, the startTime will be
            // adjusted once the delay has passed based on seek fraction.
            mSeekFraction = fraction;
        }
        mOverallFraction = fraction;
        final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
        // 執行動畫,注意這裏會先調用子類的 animateValue() 方法
        animateValue(currentIterationFraction);
    }

在看子類和父類的animateValue

//子類 ObjectAnimator#animateValue()
void animateValue(float fraction) {
        final Object target = getTarget();
        if (mTarget != null && target == null) {
            // We lost the target reference, cancel and clean up. Note: we allow null target if the
            /// target has never been set.
            cancel();
            return;
        }
        // 調用父類的 animateValue() ,這個很關鍵,時間插值與估值器的計算都在父類的 animateValue() 方法中進行的。
        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            // 這裏的 mValues 的是PropertyValuesHolder[],也就是在 PropertyValuesHolder 裏面來改變了目標 target 的屬性值。
            mValues[i].setAnimatedValue(target);
        }
    }
//父類 ValueAnimator#animateValue()
void animateValue(float fraction) {
        // 獲取時間插值
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        // 將時間插值送給估值器,計算出 values
        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);
            }
        }
    }

在看setAnimatedValue方法,通過反射來調用屬性的get和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());
            }
        }
    }

分析到這裏,就完成了動畫的一幀關鍵幀的執行。那麼你一定會感到奇怪了,剩下的幀是怎麼驅動的呢?還是得回到 start方法裏面,在這裏最初分析的 addAnimationFrameCallback方法。ValueAnimator 實現了 AnimationFrameCallback,實現了doAnimationFrame方法:

public final boolean doAnimationFrame(long frameTime) {
        .....
        boolean finished = animateBasedOnTime(currentTime);
        if (finished) {
            endAnimation();
        }
        return finished;
    }

在看animateBasedOnTime

boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            .....
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            animateValue(currentIterationFraction);
        }
        return done;
    }

這裏主要的目的也還是計算出 currentIterationFraction。然後再通過 animateValue方法來執行動畫。可以看到只要 doAnimationFrame被不斷的調用,就會產生動畫的一個關鍵幀。如果關鍵幀是連續的,那麼最後也就產生了我們所看到的動畫

再來分析doAnimationFrame 是如何被不斷調用的。這個需要回到 AnimationHandler 中來,在 AnimationHandler 中有一個非常重要的 callback 實現:Choreographer.FrameCallback

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

這裏監聽的是Vsync信號,每 1 秒裏收到 60 次回調,在這裏就實現了不斷地調用 doAnimationFrame() 來驅動動畫了。

到這裏,屬性動畫的整個過程就大體走了一遍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章