關於Android中動畫探究(二)——屬性動畫

屬性動畫

參考資源:屬性動畫 ValueAnimator 運行原理全解析

上一節我們看過了Animation視圖動畫的原理淺析,瞭解到Animation的原理是通過ViewRootImpl來監聽下一個屏幕刷新信號,然後DecorView對View樹重新繪製並且順帶將動畫繪製出來。

關於屬性動畫有兩個常用類ValueAnimator和它的子類ObjectAnimator,還有view.animate()會得到一個ViewPropertyAnimator對象,需要自己重寫其updateListener做動畫效果。

基本使用

ValueAnimator本身是沒有動畫效果的,需要用戶自定義listener,獲取到計算好的animatedValue進行賦值給view。

val animator1 = ValueAnimator.ofFloat(0f, 1.5f)
animator1.duration = 1000
animator1.repeatCount = 3
animator1.repeatMode = ValueAnimator.REVERSE
animator1.addUpdateListener {
    val data = it.animatedValue as Float
    testAnimal.scaleX = data
    testAnimal.scaleY = data
}

ObjectAnimator繼承於ValueAnimator,提供了對改變View屬性的動畫支持。官網解釋爲需要適合的get/set屬性方法去設置這個值,也就是這個屬性在View類是存在的,如果不存在也可以自己去寫屬性的get/set方法。需要注意的是ObjectAnimator也有ofFloat、ofInt、ofArgb等方法,需要確定屬性的返回值是否一致,而且屬性名要駱駝命名法。

// translationY、scaleX、scaleY、alpha等是View類的基本屬性
val animator1 = ObjectAnimator.ofFloat(testAnimal, "translationY", 0f, -120f)
val animator2 = ObjectAnimator.ofFloat(testAnimal, "scaleX", 0f, -120f)

class View {
    @ViewDebug.ExportedProperty(category = "drawing")
    public float getTranslationY() {
        return mRenderNode.getTranslationY();
    }
    public void setTranslationY(float translationY) {
        //...
    }
    //scaleX同上
}

進階用法

(1)自定義屬性動畫

View中width和height是沒有set方法的,但是動畫需要改變View的寬度(scale是拉伸View),這時候需要包裝屬性的set方法,或者使用ValueAnimator重寫動畫的updateListener。

// 第一種 包裝屬性
val animator1 = ObjectAnimator.ofInt(ViewWrapper(btn_start2), "width", 120, 420)
animator1.duration = 1000
private inner class ViewWrapper(val target: View) {
        fun getWidth(): Int {
            return target.layoutParams.width
        }
        fun setWidth(width: Int) {
            target.layoutParams.width = width
            target.requestLayout()
        }
}

// 第二種 ValueAnimator重寫動畫
// 可以使用估值器計算值的百分比,在之後的動畫原理會講到TypeEvaluator
val animator1 = ValueAnimator.ofInt(120, 420)
animator1.duration = 1000
animator1.addUpdateListener {
    val data = it.animatedValue as Int
    testAnimal.layoutParams.width = data
    testAnimal.requestLayout()
}
(2)AnimatorSet組合動畫

Animator同樣會有組合動畫,並且可對多個View進行動畫,設置動畫的執行順序,更能操控動畫的執行過程。

// Node包裝了動畫的節點,對動畫進行分類成父動畫、子動畫、兄弟動畫
animatorSet = AnimatorSet()
val animator1 = ObjectAnimator.ofInt(ViewWrapper(view2), "width", 120, 420)
val animator3 = ValueAnimator.ofFloat(0f, 1.5f)
animator3.addUpdateListener {
    val data = it.animatedValue as Float
    view1.scaleX = data
    view1.scaleY = data
}
animatorSet.duration = 1000
animatorSet.play(animator1).after(animator3)


/** class AnimatorSet */
// 同時執行的動畫
public Builder with(Animator anim) {
    Node node = getNodeForAnimation(anim);
    mCurrentNode.addSibling(node);
    return this;
}

// 之前執行的動畫
public Builder before(Animator anim) {
    Node node = getNodeForAnimation(anim);
    mCurrentNode.addChild(node);
    return this;
}

// 之後執行的動畫
public Builder after(Animator anim) {
    Node node = getNodeForAnimation(anim);
    mCurrentNode.addParent(node);
    return this;
}

Animator動畫原理解析

瞭解到ObjectAnimator繼承於ValueAnimator,我們直接從ValueAnimator看。

private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) { // 保證線程與Looper關聯
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards; //playBackwards傳入參數爲true或false,表示是否動畫反向播放
    mSelfPulse = !mSuppressSelfPulseRequested;
    if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
        if (mRepeatCount == INFINITE) {
            float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
            mSeekFraction = 1 - fraction;
        } else {
            mSeekFraction = 1 + mRepeatCount - mSeekFraction;
        }
    }
    mStarted = true;
    mPaused = false;
    mRunning = false;
    mAnimationEndRequested = false;
       
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    // 添加Animation回調
    addAnimationCallback(0);

    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        startAnimation(); // 做一些動畫的初始化工作
        if (mSeekFraction == -1) {
            setCurrentPlayTime(0); 
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    // 注意這裏傳入的是this,意味着會在ValueAnimator中回調
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}

AnimationHandler用於確保動畫使用計算後的同一值,保證動畫的同步執行

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            //記住這裏哈  等下還要回來的
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

/** 註冊callBack以獲取下一幀延時回調
*/
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    // 當前的mAnimationCallbacks爲空的話(第一次執行動畫)
    if (mAnimationCallbacks.size() == 0) {
        getProvider().postFrameCallback(mFrameCallback);
    }
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }
    if (delay > 0) {
        mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
    }
}

private AnimationFrameCallbackProvider getProvider() {
    if (mProvider == null) {
        mProvider = new MyFrameCallbackProvider();
    }
    return mProvider;
}

/** 
* 從這裏可以看到Choreographer類,這個類我們在Animation和View的繪製過程中都可以看到。
* 這個類做了什麼?
* Choregrapher類主要來控制同步處理輸入、動畫、繪製三個UI操作,也就是當屏幕的每一次刷新信號到來時要去處理某
* 些事
*    mFrameInfo.markInputHandlingStart();
*    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
*    mFrameInfo.markAnimationsStart();
*    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
*    mFrameInfo.markPerformTraversalsStart();
*    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
*    // 遍歷上述事件完成,提交操作,用於修正動畫的啓動時間
*    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
*/
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

    final Choreographer mChoreographer = Choreographer.getInstance();

    @Override
    public void postFrameCallback(Choreographer.FrameCallback callback) {
        // 發送禎回調通知,以在下一禎時回調
        mChoreographer.postFrameCallback(callback);
    }
    // ...
}

/** 回到上面說的地方,下一幀通知過來了,正式去處理動畫內容
*/
private void doAnimationFrame(long frameTime) {
    long currentTime = SystemClock.uptimeMillis();
    final int size = mAnimationCallbacks.size();
    for (int i = 0; i < size; i++) {
        // 這裏就是上面所說ValueAnimator添加callback,傳入this
        final AnimationFrameCallback callback = mAnimationCallbacks.get(i); 
        if (callback == null) {
            continue;
        }
        if (isCallbackDue(callback, currentTime)) { // 判斷動畫是否過期
            callback.doAnimationFrame(frameTime); // 調用ValueAnimator的doAnimationFrame回調 
            if (mCommitCallbacks.contains(callback)) {
                getProvider().postCommitCallback(new Runnable() {
                    @Override
                    public void run() {
                        commitAnimationFrame(callback, getProvider().getFrameTime());
                    }
                });
            }
        }
    }
    cleanUpList(); // 動畫執行話,清除callBack列表
}

ValueAnimator類中真正執行動畫的過程,與Animation一致,都是通過估值器、插值器等計算出百分比值等,在動畫的Update中去繪製。

 public final boolean doAnimationFrame(long frameTime) {
	 // ..省略代碼,概括爲處理第一幀的時間	
     //動畫中第一幀時間可能會早於當前時間,這裏主要修正當前時間,避免以負值作爲幀的顯示
     final long currentTime = Math.max(frameTime, mStartTime);
     boolean finished = animateBasedOnTime(currentTime); // 計算,顯示下一幀動畫
    
     if (finished) {
         endAnimation();
     }
     return finished;
 }

 /** 爲動畫處理單個幀操作,並且返回值標識動畫是否結束
 */ 
 boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            //省略代碼  計算動畫進度,保證動畫進度處於0~1之間,如果有設置repeatCount屬性,則這個值是會超過1的,所以需要經過計算轉換爲0~1
            animateValue(currentIterationFraction); // 對應Animation的applyTransformation()方法
        }
        return done;
    }

 /** 清除Animation的回調,回覆lastFramTime、mStarted、mRunning等屬性值
 */
 private void endAnimation() {
     if (mAnimationEndRequested) {
         return;
     }
     removeAnimationCallback();
     // 省略大法
 }

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); // 設置View屬性的地方
    }
    if (mUpdateListeners != null) { // 如果Animator有添加updateListener方法,則操作地方在這裏
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}

關於mValues[i].calculateValue(fraction)的代碼,等有時間再去深究其中代碼,這裏已交代完了Animator的執行流程,下圖是Animator的調用流程圖(手動盜圖)

屬性動畫的優點

  1. 動畫的執行整體可控制,可以添加listener進行控制動畫過程
  2. 動畫不限於自有屬性,可自定義屬性進行動畫實現
  3. 可以改變View的屬性,不僅限於只繪製其位置
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章