天天看點

從螢幕脈沖角度徹底了解屬性動畫原理

一.有個細節,就是設定不同內插補點器或者估值的,可以定義一個接口,不同的內插補點器比如線性插值器估值器,定義好,在主要也就是上層實作動畫的時候,直接調用不同的內插補點器就行了,因為上層的需求就是一個插值器,調用的設定不同的內插補點器就行了,面向接口程式設計。

二.整個動畫原理流程:

1.先明白2個概念,幀重新整理率和螢幕重新整理率,幀重新整理率相當于在咱的應用中,經過cpu執行測量,布局和繪制流程,對圖像轉換成文理,再經過gpu對圖像渲染,其實就是放到一個緩沖區中,這個時候還沒有顯示;

顯示就需要用到螢幕重新整理率了,螢幕重新整理率也就是螢幕每隔16ms發送一個脈沖信号,隻有vsync脈沖信号到了,才會去緩沖區拿顯示的那些資料來顯示。

下邊正式開始:

首先,開始動畫,

private void scheduleAnimation() {

    if (!mAnimationScheduled) {

mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);

        mAnimationScheduled = true;

    }

}

發送一個同步信号,這個同步信号會在底層進行注冊,有個信号回調,代表下一幀也就是底層的螢幕重新整理脈沖信号也就是16ms調用一次的機制,會回調我注冊的一個回調監聽,記住,發送一個這個同步信号,隻是一次有效,這個也沒什麼用,像動畫的這個回調裡,就是動畫機制自己處理了,隻是使用了脈沖的回調的時間,

scheduleVsyncLocked

下邊的及其核心

public void scheduleVsync() {

    if (mReceiverPtr == 0) {

        Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "

                + "receiver has already been disposed.");

    } else {

        nativeScheduleVsync(mReceiverPtr);

    }

}

仔細看英文,就是發送一個垂直同步信号,當下一幀顯示的時候開始的時候,會回調。

當scheduleAnimation調用了以後,會在下一個16ms回調animationHandler的run方法,

// Called by the Choreographer.

@Override

public void run() {

    mAnimationScheduled = false;

    doAnimationFrame(mChoreographer.getFrameTime());

}

繼續看注釋,這個回調,是在編舞者的脈沖回來的時候調用的,間隔了16ms才調用的,

private void doAnimationFrame(long frameTime) {

    // mPendingAnimations holds any animations that have requested to be started

    // We're going to clear mPendingAnimations, 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 mPendingAnimations

    // is empty.

    while (mPendingAnimations.size() > 0) {

        ArrayList<ValueAnimator> pendingCopy =

                (ArrayList<ValueAnimator>) mPendingAnimations.clone();

        mPendingAnimations.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(this);

            } else {

                mDelayedAnims.add(anim);

            }

        }

    }

    // Next, process animations currently sitting on the delayed queue, adding

    // them to the active animations if they are ready

    int numDelayedAnims = mDelayedAnims.size();

    for (int i = 0; i < numDelayedAnims; ++i) {

        ValueAnimator anim = mDelayedAnims.get(i);

        if (anim.delayedAnimationFrame(frameTime)) {

            mReadyAnims.add(anim);

        }

    }

    int numReadyAnims = mReadyAnims.size();

    if (numReadyAnims > 0) {

        for (int i = 0; i < numReadyAnims; ++i) {

            ValueAnimator anim = mReadyAnims.get(i);

            anim.startAnimation(this);

            anim.mRunning = true;

            mDelayedAnims.remove(anim);

        }

        mReadyAnims.clear();

    }

    // Now process all active animations. The return value from animationFrame()

    // tells the handler whether it should now be ended

    int numAnims = mAnimations.size();

    for (int i = 0; i < numAnims; ++i) {

        mTmpAnimations.add(mAnimations.get(i));

    }

    for (int i = 0; i < numAnims; ++i) {

        ValueAnimator anim = mTmpAnimations.get(i);

        if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {

            mEndingAnims.add(anim);

        }

    }

    mTmpAnimations.clear();

    if (mEndingAnims.size() > 0) {

        for (int i = 0; i < mEndingAnims.size(); ++i) {

            mEndingAnims.get(i).endAnimation(this);

        }

        mEndingAnims.clear();

    }

    // If there are still active or delayed animations, schedule a future call to

    // onAnimate to process the next frame of the animations.

    if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {

        scheduleAnimation();

    }

}

當脈沖回來了,會走run的回調,會調用這個處理動畫的邏輯,這個邏輯有兩個地方需要記住的:

1.anim.doAnimationFrame,循環周遊已經準備好的動畫,根據時間流逝的百分比,來設定屬性值,也就達到了16ms設定一次動畫值的效果和目的和流程。

2.scheduleAnimation,如果還有動畫,在處理完動畫以後,還會繼續調用開始動畫,也就是發送一個動畫的脈沖,這樣就相當于每隔16ms調用一次run的doAnimationFrame了,處理動畫。

三.細節,計算動畫的值部分,了解keyframe

根據時間,計算出來百分比,時間的流逝,在前邊的poertryholder裡,儲存了每個關鍵幀的比例和時間,比如(0,100,200),keyframe的key的百分比,就是0,二分之一,1,還有對應的0,100,200。

而此時動畫的值,就是時間流逝的百分比*前後幀值的間隔。

這個就是關鍵幀的計算法:

比如前一幀和下一幀,fraction代表當下的百分比,前一幀的開始百分比,下一幀的百分比值,是以求出來的結果值就是現在的百分比減去前一幀的百分比代表間隔,除以後幀減去前幀,計算出來的結果就是百分比,再乘以前幀和後幀的值的內插補點,就是目前的值。

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

這個就是關鍵幀的計算法:

比如前一幀和下一幀,fraction代表當下的百分比,前一幀的開始百分比,下一幀的百分比值,是以求出來的結果值就是現在的百分比減去前一幀的百分比代表間隔,除以後幀減去前幀,計算出來的結果就是百分比,再乘以前幀和後幀的值的內插補點,就是目前的值。

            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),

                    nextKeyframe.getValue());

        }

        prevKeyframe = nextKeyframe;

    }

    // shouldn't reach here

    return mLastKeyframe.getValue();

}