一、前言
Android動畫包含三種:補間動畫(Tween Animation),幀動畫(Frame Animation),屬性動畫 (Property Animation)。其中屬性動畫是從Android 3.0之后加入的。
本文着重介紹三種動畫的實現原理,閱讀本文的前提是應該可以簡單的使用上述三種動畫。
如不熟悉可以參考以下鏈接,
二、補間動畫原理(Tween Animation)
原理:在繪制的過程中,
嘗試獲取動畫在當前時刻的變換,
然后應用到view的繪制中。
說明:
繪制是顯示view所必不可少的過程,通過view的draw方法可以看到繪制的流程。
查看View.java的源碼,可以發現有兩個draw方法,一個有一個參數,一個有三個參數。分別是
public void draw(Canvas canvas)
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)
其中一個參數的draw方法是具體的繪制過程(繪制背景、繪制內容、繪制前景...)
含有三個參數的draw方法是由父布局調用的(ViewGroup.drawChild),因為Android繪制的過程是從根布局開始,
子View是否能夠繪制由父布局決定。這個方法就是提前處理繪制的位置的地方,補間動畫也是在此處進行了處理以實現動畫的效果。
先舉一個例子,
Animation translateAnimation = new TranslateAnimation(0, 100, 0, 0);
translateAnimation.setDuration(500);
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setFillAfter(true);//設置動畫結束后保持當前的位置(即不返回到動畫開始前的位置)
imageView.startAnimation(translateAnimation);
一個動畫重要的東西就四個部分,開始時間,結束時間,做什么,如何做。
上述是一個imageView在x方向移動的動畫,可以看到它符合上面所說的四個部分,
做什么:沿x方向移動100
如何做:直線且不斷加速
開始時間:startAnimtion調用的那一刻
結束時間: 開始時間+500
從上面也大致了解到補間動畫的框架是通過先建立一個Animation對象並對其設置一些屬性,然后將它與一個view建立關聯,
最后這個view可以執行這個動畫。
由此我們開始分析Animation的源碼(基於Android 6.0):
Animation及相關類的源碼在frameworks/base/core/java/android/view/animation包中。
其中Animation類是一個抽象類,雖然它沒有抽象方法,但是它有一個空方法
protected void applyTransformation(float interpolatedTime, Transformation t)
可以看到,它有兩個參數,第一個參數是 interpolatedTime 它代表插值后的時間,第二個參數是Transformation類的實例
Transformation是一個實體類,它主要的內容是透明度和一個矩陣。
所以子類實現了applyTransformation方法后可以針對插值時間來對Transformation做一定的操作來實現變化。
同時可以看到
applyTransformation是在Animtaion的getTransformation中調用的,
public boolean getTransformation(long currentTime, Transformation outTransformation)
其中currentTime作為一個參數,雖然期望的是當前時間,但是不是用還是由調用者決定。
第二個參數目的是調用者傳進來作為收集變化信息
此方法除了調用回調(開始,結束,重復),重點是調用applyTransformation前的這一句
final float interpolatedTime = mInterpolator.
getInterpolation(normalizedTime);
插值器的目的是為了將控制動畫速度的過程抽離出來,它是通過改變時間來改變最終效果。
normalizedTime 的范圍是0.0f~1.0f
但是interpolatedTime可以超出這個范圍
所以在實現applyTransformation的時候需要考慮插值后的時間這個范圍之外的情況。
下面以TranslateAnimation和AccelerateInterpolator來說明是如何實現的
可以知道
TranslateAnimation主要實現
applyTransformation方法
AccelerateInterpolator主要實現
getInterpolation方法
下面是兩個方法的源碼:
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
}
t.getMatrix().setTranslate(dx, dy);//改變了Transformation的矩陣偏移
}
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input; //輸入0.5 返回0.25
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
如此一來,動畫的變化,就隱藏在Transformation之中了。
既然已經知道了如何變化,現在就需要系統使用這種變化即應用到繪制中了。
移步到View.java之中的draw(--,--,--)方法,
其中applyLegacyAnimation方法是用來獲取變換的,
其中有兩個部分需要注意
a.getTransformation(drawingTime, invalidationTransform, 1f);
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);
第一個就是獲取變換,第二個是獲取繪制無效區域。
其實這個無效區域是繪制后的無效區域,因為很有可能需要下次繪制(保證動畫連續性)。
而view在draw之中
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
// Undo the scroll translation, apply the transformation matrix,
// then redo the scroll translate to get the correct result.
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
分別從硬件加速和軟件繪制上 對canvas進行矩陣變換.
除了TranslateAnimation,系統還有AlphaAnimation和ClipRectAnimation可供選擇.
三、逐幀動畫原理(Frame Animation)
原理:使用了Choreographer機制
AnimationDrawable類是一個實現了逐幀動畫的類,可以看出,它只用來進行圖片的動態切換.
AnimationDrawable類源碼在frameworks/base/graphics/java/android/graphics/drawable/中
public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable
首先看到AnimationDrawable繼承了DrawableContainer,因為DrawableContainer是一個drawable的容器,可以保存多個圖片
同時,實現了Runnable接口,重寫了run方法
根據源碼中的start方法,它調用了setFrame方法,方法內部最重要的調用就是調用了
scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
上述方法實現是在Drawable類實現的
public void scheduleSelf(Runnable what, long when) {
final Callback callback = getCallback();
if (callback != null) {
callback.scheduleDrawable(this, what, when);
}
}
其中callback一般是drawable相關聯的view.
可以看出,它接着回調了view的scheduleDrawable方法
而這個方法最終會
mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
Choreographer.CALLBACK_ANIMATION, what, who,
Choreographer.subtractFrameDelay(delay));
來實現delay后,可以繪制下一幀的效果.
因為AnimationDrawable所實現了runnable接口的run方法就是執行nextFrame.
同時,AnimatedStateListDrawable和AnimatedVectorDrawable和AnimatedRotateDrawable(隱藏)
都是具備一定的動畫效果
其中,AnimatedStateListDrawable是在view狀態切換時可以實現兩個狀態直接的漸變
如果想了解更多,需要對drawable有所了解.
四、屬性動畫原理(Property Animation)
原理:使用了Choreographer機制
簡單的說,Chreographer是組織上層進行處理繪制的控制類,它會在每次vsync信號來臨時,執行與繪制相關的過程.
屬性動畫相關的方法在源碼中所在的位置是frameworks/base/core/java/android/view/animation
屬性動畫的基類是Animator.
與Animation(補間動畫)不同,Animator的確定過程發生在繪制之前(甚至是布局之前).
因為ValueAnimator引入了Choreographer,Choreographer是Vsync信號到來后進行view更新的控制類。
它通過mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
這個動作來實現下次繪制之前,可以執行
mAnimate任務來設置view的一些屬性來實現動畫。
上述所說的view的一些屬性包括X,translateX,TranslateZ,ScaleX......等屬性
對這些屬性的控制都會放到一個矩陣(Matrix),而這個矩陣放在RenderNode中
需要注意的是,雖然RenderNode主要為硬件渲染服務,但是它保存了一些屬性是軟件渲染也需要的,比如上面說的矩陣.
通過硬件加速繪制時,因為矩陣已經在RenderNode中了,所以在draw方法中不用做特殊處理.
而在軟件渲染中(draw方法),
if (!childHasIdentityMatrix && !drawingWithRenderNode) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
}
其中,childHasIdentityMatrix 代表是不是單位矩陣
drawingWithRenderNode 代表是不是開啟了硬件加速
說明:
觀察ValueAnimator的源碼
發現方法
private void scheduleAnimation() {
if (!mAnimationScheduled) {
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
mAnimationScheduled = true;
}
}
以及mAnimate為
// Called by the Choreographer.
final Runnable mAnimate = new Runnable() {
@Override
public void run() {
mAnimationScheduled = false;
doAnimationFrame(mChoreographer.getFrameTime());
}
};
你如果對ValueAnimator添加了更新監聽(addUpdateListener)
那么你可以在每次更新的回調(發生在上面的doAnimationFrame里[實際是animateValue])
比如進行view.setTranslateX(10),這種處理在隨后的繪制中就會有所體現
參考文檔:
