概述
上一篇講到了屬性動畫,主要講到了用法和大概的思路。但是沒有講到具體是如何實現動畫的。這里我們分析下View動畫和屬性動畫的源碼,下一篇分析下動畫中非常重要的插值器和估值器。最后總結下如何自己寫一個動畫效果。
View動畫的源碼及實現
我們知道view.startAnimation(anim);是動畫開始的入口。這段代碼并沒有繪制的代碼,那么是如何實現動畫的呢。其實關鍵就在invalidate的時候。在這個方法中會重新繪制了畫面,隨帶著計算了動畫控件下一幀出現的位置。
public void startAnimation(Animation animation) {
// 在animation中設置時間為START_ON_FIRST_FRAME,這表示在刷新第一幀的時候開始動畫
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
// 重置參數,如果鎖屏的話設置開始時間為當前,不然會在亮屏后會開始動畫
setAnimation(animation);
invalidateParentCaches();
// 重繪開始,刷新幀
invalidate(true);
}
我們來看下這個方法調用的周期圖,可以看到主要是重新調用了draw的相關方法。那么我們就找一找哪里調用了animation
最后在ondraw方法中發現了如下幾行代碼,這說明在draw方法中拿到了動畫的對象,然后去執行一些操作。主要的代碼在applyLegacyAnimation()中,點進去看下。
// 獲得設置的Animation
final Animation a = getAnimation();
if (a != null) {
// 動畫的主要方法
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
}
applyLegacyAnimation中代碼比較復雜,如果動畫沒有初始化,那么先初始化。a.getTransformation(drawingTime, invalidationTransform, 1f)調用了Animation中的代碼。其實上面還有一個疑問,就是draw方法中transformToApply = parent.getChildTransformation();中如何拿到的Transformation。從這里就可以找到答案,final Transformation t = parent.getChildTransformation();這個方法獲取了父控件的Transformation對象,如果沒有,父控件會創建一個。
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
if (!initialized) {
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
onAnimationStart();
}
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = t;
}
下面代碼在Animaiton中,主要是計算插值后的時間進入,然后進行變換操作。首先,判斷是否是第一次,如果是第一次設置開始時間為當前時間。然后獲取duration等的一些參數。根據當前時間,開始時間和動畫時長就可以計算出當前的時間進度。很重要的一點,就是會拿到插值器Interpolator去計算插值后的進度。最后調用applyTransformation方法。
/**
* Gets the transformation to apply at a specified point in time. Implementations of this
* method should always replace the specified Transformation or document they are doing
* otherwise.
*
* @param currentTime Where we are in the animation. This is wall clock time.
* @param outTransformation A transformation object that is provided by the
* caller and will be filled in by the animation.
* @return True if the animation is still running
*/
public boolean getTransformation(long currentTime, Transformation outTransformation) {
// 在startAnimation的時候我們設置了Animation.START_ON_FIRST_FRAME,值為-1,第一次調用設置為當前時間
if (mStartTime == -1) {
mStartTime = currentTime;
}
// 獲取延時
final long startOffset = getStartOffset();
// 獲取執行時長
final long duration = mDuration;
float normalizedTime;
// 獲取當前時間時,已經經歷的動畫時長占的百分比
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// time is a step-change with a zero duration
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}
// 判斷是否已經完成了
final boolean expired = normalizedTime >= 1.0f;
// 是否超時,沒有的話設置有更多的標志
mMore = !expired;
// 當我們設置不保持在動畫開始之前的話,那么會設置到時間1時刻的狀態。
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
// 開始動畫
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
if (!mStarted) {
// 回調listener中的開始方法
fireAnimationStart();
mStarted = true;
if (USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}
if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if (mCycleFlip) {
normalizedTime = 1.0f - normalizedTime;
}
// 從插值其中拿到插值后的時間進度
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
// 執行變換操作
applyTransformation(interpolatedTime, outTransformation);
}
// 動畫結束后的判斷
if (expired) {
if (mRepeatCount == mRepeated) {
if (!mEnded) {
mEnded = true;
guard.close();
fireAnimationEnd();
}
} else {
if (mRepeatCount > 0) {
mRepeated++;
}
if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}
mStartTime = -1;
mMore = true;
fireAnimationRepeat();
}
}
if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}
return mMore;
}
從上面的代碼可以看出,最后執行的是applyTransformation方法,我們進去看一下,發現是抽象類。由于Animation是一個抽象類,所以我們看下TranslateAnimation類里面的實現。這里邏輯就非常清晰了,這個方法中只是將outTransformation 做了一個平移的變換。
據此猜測,View動畫效果的實現主要是靠Matrix矩陣來實現的,所以這種動畫沒法改變點擊區域的情況的原因就十分明朗了。
@Override
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);
}
執行完這個方法以后,就應該返回View里面的applyLegacyAnimation方法了,返回值是是否還有未執行完的動畫。如果有的話會執行下面這段代碼。主要的是 a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,invalidationTransform);這一行代碼,主要是獲得控件的重新繪制所在的位置。
if (more) {
if (!a.willChangeBounds()) {
if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
} else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
parent.invalidate(mLeft, mTop, mRight, mBottom);
}
} else {
if (parent.mInvalidateRegion == null) {
parent.mInvalidateRegion = new RectF();
}
final RectF region = parent.mInvalidateRegion;
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
進入getInvalidateRegion方法,主要是去根據Transformation 中的參數,去重新拿到繪制區域。保存Transformation。
public void getInvalidateRegion(int left, int top, int right, int bottom,
RectF invalidate, Transformation transformation) {
final RectF tempRegion = mRegion;
final RectF previousRegion = mPreviousRegion;
invalidate.set(left, top, right, bottom);
transformation.getMatrix().mapRect(invalidate);
// Enlarge the invalidate region to account for rounding errors
invalidate.inset(-1.0f, -1.0f);
tempRegion.set(invalidate);
invalidate.union(previousRegion);
previousRegion.set(tempRegion);
final Transformation tempTransformation = mTransformation;
final Transformation previousTransformation = mPreviousTransformation;
tempTransformation.set(transformation);
transformation.set(previousTransformation);
previousTransformation.set(tempTransformation);
}
然后就會退出到draw方法中,接下去執行中,會拿到一個Transformation類型的transformToApply的對象,這個對象保存著我們最終要執行的動畫。然后ondraw方法中會調用如下代碼,可以看到,最后通過canvas對象執行了一系列的動畫操作。
if (transformToApply != null
|| alpha < 1
|| !hasIdentityMatrix()
|| (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (transformToApply != null || !childHasIdentityMatrix) {
int transX = 0;
int transY = 0;
if (offsetForScroll) {
transX = -sx;
transY = -sy;
}
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;
}
}
if (!childHasIdentityMatrix && !drawingWithRenderNode) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
}
}
到這里View動畫的過程大致分析完了。最主要的過程是通過每次invalidate來獲取時間,然后通過Animation計算出動畫的執行到了哪里,相應的設置Transformation中Matrix的變換參數。然后在Animation中也保存一些繪制區域和Transformation等參數。最后回到draw函數中,看是否有變換且需要去執行(transformToApply ),有的話執行平移、縮放和透明度操作。
屬性動畫
前面已經介紹了View動畫,但是這和屬性動畫的實現方式非常的不同。接下去我們就看下屬性動畫的源碼及實現。
我們從ObjectAnimator的主入口進去,這段代碼還是比較長的,但是邏輯比較簡單,首先看是否有執行中的,等待的和延遲的動畫,若有則全部取消。然后調用父類的start方法。
@Override
public void start() {
// See if any of the current active/pending animators need to be canceled
AnimationHandler handler = sAnimationHandler.get();
if (handler != null) {
int numAnims = handler.mAnimations.size();
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}
numAnims = handler.mPendingAnimations.size();
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}
numAnims = handler.mDelayedAnims.size();
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}
}
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();
}
進入父類的start方法,首先這個方法需要一個looper,如果沒有looper則動畫無法執行。然后是一些參數的初始化。最后會調用animationHandler.start();
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
mPlayingBackwards = playBackwards;
if (playBackwards && mSeekFraction != -1) {
if (mSeekFraction == 0 && mCurrentIteration == 0) {
// special case: reversing from seek-to-0 should act as if not seeked at all
mSeekFraction = 0;
} else if (mRepeatCount == INFINITE) {
mSeekFraction = 1 - (mSeekFraction % 1);
} else {
mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
}
mCurrentIteration = (int) mSeekFraction;
mSeekFraction = mSeekFraction % 1;
}
if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
(mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
// if we were seeked to some other iteration in a reversing animator,
// figure out the correct direction to start playing based on the iteration
if (playBackwards) {
mPlayingBackwards = (mCurrentIteration % 2) == 0;
} else {
mPlayingBackwards = (mCurrentIteration % 2) != 0;
}
}
int prevPlayingState = mPlayingState;
mPlayingState = STOPPED;
mStarted = true;
mStartedDelay = false;
mPaused = false;
updateScaledDuration(); // in case the scale factor has changed since creation time
AnimationHandler animationHandler = getOrCreateAnimationHandler();
animationHandler.mPendingAnimations.add(this);
if (mStartDelay == 0) {
// This sets the initial value of the animation, prior to actually starting it running
if (prevPlayingState != SEEKED) {
setCurrentPlayTime(0);
}
mPlayingState = STOPPED;
mRunning = true;
notifyStartListeners();
}
animationHandler.start();
}
AnimationHandler animationHandler = getOrCreateAnimationHandler();這里有一個Handler對象,我們先來看下這個Handler是什么,在這個內部靜態類的上方官方給了一段解釋
- This custom, static handler handles the timing pulse that is shared by
- all active animations. This approach ensures that the setting of animation
- values will happen on the UI thread and that all animations will share
- the same times for calculating their values, which makes synchronizing
- animations possible.
- The handler uses the Choreographer for executing periodic callbacks.
我想這段話的意思很明白,就是給了一個統一的心跳一樣的handler。
start方法最后會定時調用Runnable方法,我們發現最終執行的是doAnimationFrame方法。
final Runnable mAnimate = new Runnable() {
@Override
public void run() {
mAnimationScheduled = false;
doAnimationFrame(mChoreographer.getFrameTime());
}
};
省略了一些內容,最后發現是調用的doAnimationFrame
void doAnimationFrame(long frameTime) {
...
// 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);
}
}
...
調用了commitAnimationFrame,這個方法最后調用了animationFrame。可以發現,正是animationFrame這個方法中計算了時間的fraction。這個值對任何動畫來說都是相同的,代表著固定的時間流逝。然后調用了animateValue。
boolean animationFrame(long currentTime) {
boolean done = false;
switch (mPlayingState) {
case RUNNING:
case SEEKED:
float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
if (mDuration == 0 && mRepeatCount != INFINITE) {
// Skip to the end
mCurrentIteration = mRepeatCount;
if (!mReversing) {
mPlayingBackwards = false;
}
}
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;
}
mCurrentIteration += (int) fraction;
fraction = fraction % 1f;
mStartTime += mDuration;
// Note: We do not need to update the value of mStartTimeCommitted here
// since we just added a duration offset.
} else {
done = true;
fraction = Math.min(fraction, 1.0f);
}
}
if (mPlayingBackwards) {
fraction = 1f - fraction;
}
animateValue(fraction);
break;
}
return done;
}
這個方法比較短,但是非常重要,正是這里通過mInterpolator計算出插值。
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);
}
}
}
然后調用了 mUpdateListeners.get(i).onAnimationUpdate(this);通過mValues[i].calculateValue(fraction);調用holder對象的方法,通過onAnimationUpdate來告訴監聽者已經刷新了。
ObjectAnimator重寫了animateValue這個方法
@Override
void animateValue(float fraction) {
final Object target = getTarget();
if (mTarget != null && target == null) {
// We lost the target reference, cancel and clean up.
cancel();
return;
}
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(target);
}
}
調用PropertyValuesHolder的setAnimatedValue方法,最終是調用 mSetter.invoke(target, mTmpValueArray);
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());
}
}
}
總結
這篇介紹了View動畫和屬性動畫的源碼,下篇具體介紹下如何寫一個自定義的動畫。