補(bǔ)間動(dòng)畫源碼中分析機(jī)制原理
前言
補(bǔ)間動(dòng)畫移動(dòng)后,點(diǎn)擊事件的響應(yīng)為什么還在原來的位置?
那今天我們就來從源碼解析原理
一、補(bǔ)間動(dòng)畫
補(bǔ)間動(dòng)畫可以在一個(gè)視圖容器內(nèi)執(zhí)行一系列簡(jiǎn)單變換(具體的變換步驟有:位置、大小、旋轉(zhuǎn)、透明度);
我們可以通過平移、旋轉(zhuǎn)、縮放、透明度等API進(jìn)行具體的操作;
補(bǔ)間動(dòng)畫的實(shí)現(xiàn)方式可以通過 XML或通過Android代碼兩種方式 去定義;
1、xml方式實(shí)現(xiàn)
文件名:animator_translate.xml
- <?xml version="1.0" encoding="utf-8"?>
- <translate
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:fromXDelta="0"
- android:fromYDelta="0"
- android:toYDelta="0"
- android:toXDelta="200"
- android:duration="500"
- android:fillAfter="true">
- </translate>
代碼加載xml文件獲取動(dòng)畫
- //加載動(dòng)畫
- Animation animation = AnimationUtils.loadAnimation(this, R.anim.animator_translate);
- //執(zhí)行動(dòng)畫
- testBtn.startAnimation(animation);
2、代碼方式實(shí)現(xiàn)
- TranslateAnimation translateAnimation = new TranslateAnimation(0,200,0,0);
- translateAnimation.setDuration(500);//動(dòng)畫執(zhí)行時(shí)間
- translateAnimation.setFillAfter(true);//動(dòng)畫執(zhí)行完成后保持狀態(tài)
- //執(zhí)行動(dòng)畫
- testBtn.startAnimation(translateAnimation);
二、補(bǔ)間動(dòng)畫原理解
1、startAnimation
startAnimation(rotateAnimation)方法進(jìn)入源碼;
- //View.java
- public void startAnimation(Animation animation) {
- animation.setStartTime(Animation.START_ON_FIRST_FRAME);
- setAnimation(animation);
- invalidateParentCaches();
- invalidate(true);
- }
首先是通過setStartTime()設(shè)置了動(dòng)畫的開始時(shí)間;
- //View.java
- public void setStartTime(long startTimeMillis) {
- mStartTime = startTimeMillis;
- mStarted = mEnded = false;
- mCycleFlip = false;
- mRepeated = 0;
- mMore = true;
- }
這里只是對(duì)一些變量進(jìn)行賦值,再來看看下一個(gè)方法;
設(shè)置動(dòng)畫setAnimation(animation):
- //View.java
- public void setAnimation(Animation animation) {
- mCurrentAnimation = animation;
- if (animation != null) {
- if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
- && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
- animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
- }
- animation.reset();
- }
- }
這里面也是將動(dòng)畫實(shí)例賦值給當(dāng)前的成員變量;
分析startAnimation()方法里的invalidateParentCaches();
- //View.java
- protected void invalidateParentCaches()
- if (mParent instanceof View) {
- ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
- }
- }
可以看到這里僅僅是設(shè)置動(dòng)畫標(biāo)記,在視圖構(gòu)建或者屬性改變時(shí)是必要的;
再回到startAnimation()方法里面invalidate(true);
2、invalidate
- //View.java
- public void invalidate(boolean invalidateCache) {
- invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
- }
- void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
- boolean fullInvalidate) {
- if (mGhostView != null) {
- mGhostView.invalidate(true);
- return;
- }
- .................
- // Propagate the damage rectangle to the parent view.
- final AttachInfo ai = mAttachInfo;
- final ViewParent p = mParent;
- if (p != null && ai != null && l < r && t < b) {
- final Rect damage = ai.mTmpInvalRect;
- damage.set(l, t, r, b);
- p.invalidateChild(this, damage);
- }
- }
- }
這里著重看p.invalidateChild(this, damage);
- //ViewGroup.java
- @Deprecated
- @Override
- public final void invalidateChild(View child, final Rect dirty) {
- final AttachInfo attachInfo = mAttachInfo;
- if (attachInfo != null && attachInfo.mHardwareAccelerated) {
- // HW accelerated fast path
- onDescendantInvalidated(child, child);
- return;
- }
- ViewParent parent = this;
- .........
- do {
- View view = null;
- if (parent instanceof View) {
- view = (View) parent;
- }
- .........
- parent = parent.invalidateChildInParent(location, dirty);
- } while (parent != null);
- }
- }
- 因?yàn)閂iewParent p = mParent,this是View的子類ViewGroup;
- 所以p.invalidateChild(this, damage)里面其實(shí)是調(diào)用了ViewGroup的invalidateChild();
- 這里有一個(gè)do{}while()循環(huán),第一次的時(shí)候parent = this即ViewGroup,然后調(diào)用parent.invalidateChildInParent(location, dirty)方法,當(dāng)parent == null的時(shí)候結(jié)束循環(huán);
- invalidateChildInParent方法中,只要條件成立就會(huì)返回mParent;
- //ViewGroup.java
- @Deprecated
- @Override
- public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
- if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
- .......
- return mParent;
- }
- return null;
- }
- ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0)是保持成立的,所以會(huì)一直返回mParent,那么說明View的mParent是ViewGroup;
- ViewGroup的mParent也是ViewGroup,而do{}while()循環(huán)一直找mParent,而一個(gè)View最頂端的mParent是ViewRootImpl,所以最后走到ViewRootImpl的invalidateChildInParent()里面;
- 在onCreate()方法里面通過setContentView()將布局添加到以DecorView為根布局的一個(gè)ViewGroup里面,因?yàn)樵趏nResume()執(zhí)行完成后,WindowManager會(huì)執(zhí)行addView()方法,然后會(huì)創(chuàng)建一個(gè)ViewRootImpl對(duì)象,與DecorView綁定起來,DecorView的mParent設(shè)置成ViewRootImpl,ViewRootImpl實(shí)現(xiàn)了ViewParent接口,所以ViewRootImpl雖然沒有繼承View或者ViewGroup;
- ViewRootImpl的invalidateChildInParent()方法中;
- //ViewRootImpl.java
- @Override
- public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
- checkThread();
- if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
- if (dirty == null) {
- invalidate();
- return null;
- } else if (dirty.isEmpty() && !mIsAnimating) {
- return null;
- }
- .......
- invalidateRectOnScreen(dirty);
- return null;
- }
這里所有的返回值都變?yōu)閚ull了,之前執(zhí)行的do{}while()循壞也會(huì)停止。
3、scheduleTraversals()
- 接著分析invalidateRectOnScreen(dirty)方法;
- 進(jìn)入 scheduleTraversals()方法;
- //ViewRootImpl.java
- private void invalidateRectOnScreen(Rect dirty) {
- ......
- if (!mWillDrawSoon && (intersected || mIsAnimating)) {
- scheduleTraversals();
- }
- }
- //ViewRootImpl.java
- void scheduleTraversals() {
- if (!mTraversalScheduled) {
- mTraversalScheduled = true;
- mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
- mChoreographer.postCallback(
- Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
- if (!mUnbufferedInputDispatch) {
- scheduleConsumeBatchedInput();
- }
- notifyRendererOfFramePending();
- pokeDrawLockIfNeeded();
- }
- }
主要看mTraversalRunnable,我們找到mTraversalRunnable這個(gè)類;
- //ViewRootImpl.java
- final class TraversalRunnable implements Runnable {
- @Override
- public void run() {
- doTraversal();
- }
- }
- final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
4、doTraversal()
doTraversal()方法;
- //ViewRootImpl.java
- void doTraversal() {
- .......
- performTraversals();
- .......
- }
- }
- scheduleTraversals()是將 performTraversals()放到一個(gè)Runnable里面;
- 在Choreographer的帶執(zhí)行對(duì)列里面,這些待執(zhí)行的Runable會(huì)在最近的一個(gè)16.6ms屏幕刷新信號(hào)到來的時(shí)候執(zhí)行;
- 而performTraversals()是View的三大操作:測(cè)量、布局、繪制的發(fā)起者;
- 在Android屏幕刷新機(jī)制里,View樹里面不管哪個(gè)View發(fā)起的繪制請(qǐng)求或者布局請(qǐng)求都會(huì)走到ViewRootImpl的scheduleTraversals()里面,然后在最新的一個(gè)屏幕刷新信號(hào)來到時(shí),再通過ViewRootImpl的performTraversals()從根布局DecorView依次遍歷View樹去執(zhí)行測(cè)量、布局、繪制三大操作
- 每一次的刷新都會(huì)走到ViewRootImpl里面,然后在層層遍歷到發(fā)生改變的VIew里去執(zhí)行相應(yīng)的布局和繪制操作;
- 所以在調(diào)用View.startAnimation(rotateAnimation)后,并沒有立即執(zhí)行動(dòng)畫,而是做了一下變量初始化操作,將View和Animation綁定起來,調(diào)用重繪操作,內(nèi)部層層尋找mPartent,最終在ViewRootImpl的scheduleTraversals()發(fā)起一個(gè)遍歷View樹的請(qǐng)求,在最近一個(gè)屏幕信號(hào)刷新到來時(shí)執(zhí)行這個(gè)請(qǐng)求,調(diào)用performTraversals()從根布局去遍歷View樹。
5、draw
- 我們繼續(xù)分析draw方法是怎樣將動(dòng)畫繪制的且動(dòng)畫是怎樣動(dòng)起來的呢?
- invalidate最終會(huì)調(diào)用到ViewRootImpl 注冊(cè)Choreographer的回調(diào);
- 在下一次VSYN信號(hào)到來時(shí) 會(huì)調(diào)用ViewRootImpl performTraversals 最終會(huì)調(diào)用到View的draw方法。
- boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
- ...
- //清除上次動(dòng)畫保存的Transformation
- if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
- parent.getChildTransformation().clear();
- parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
- }
- ......
- final Animation a = getAnimation();
- if (a != null) {
- //根據(jù)當(dāng)前時(shí)間計(jì)算當(dāng)前幀的動(dòng)畫,more表示是否需要執(zhí)行更多幀的動(dòng)畫
- more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
- concatMatrix = a.willChangeTransformationMatrix();
- if (concatMatrix) {
- mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
- }
- //拿到當(dāng)前幀需要的變換 ,這個(gè)值會(huì)在applyLegacyAnimation中進(jìn)行設(shè)置
- transformToApply = parent.getChildTransformation();
- }
- ....
- 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());//在這里調(diào)用canvas的concat方法,實(shí)現(xiàn)最終的平移效果 (做矩陣相乘)
- canvas.translate(transX, transY);
- }
- //標(biāo)記需要清除Tranformation
- parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
- }
- float transformAlpha = transformToApply.getAlpha();
- if (transformAlpha < 1) {
- alpha *= transformAlpha;
- parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
- }
- }
- ...
- }
- 調(diào)用applyLegacyAnimation根據(jù)當(dāng)前時(shí)間來計(jì)算當(dāng)前幀的動(dòng)畫同時(shí)返回值表示動(dòng)畫是否還沒播放完成;
- 拿到當(dāng)前幀需要的變換transformToApply;
- 調(diào)用canvas.concat 方法 實(shí)現(xiàn)最終的平移效果 (做矩陣相乘) 這樣我們就將最終的平移想過畫到canvas上面了解決了如何完成繪制的問題;
- 接下來繼續(xù)分析applyLegacyAnimation是如何計(jì)算當(dāng)前的幀的動(dòng)畫的。
6、applyLegacyAnimation
- private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
- Animation a, boolean scalingRequired) {
- ...
- //獲取Transformation 每個(gè)ViewGroup中的子View共同使用一個(gè)Transformation 為了多個(gè)View有動(dòng)畫時(shí)頻繁創(chuàng)建多個(gè)Transformation
- //這個(gè)和在draw方法中取出的transformToApply是一個(gè)對(duì)象 就是最終應(yīng)用到Canvas上的Transform
- final Transformation t = parent.getChildTransformation();
- //調(diào)用Animation的getTransformation方法來根據(jù)當(dāng)前時(shí)間計(jì)算Transformation 這個(gè)對(duì)象的值最終會(huì)由getTransformation方法中進(jìn)行賦值
- boolean more = a.getTransformation(drawingTime, t, 1f);
- invalidationTransform = t;
- ...
- //如果動(dòng)畫還沒有播放完成 需要讓動(dòng)畫循環(huán)起來 實(shí)際上是繼續(xù)調(diào)用invalidate
- if (more) {
- if (parent.mInvalidateRegion == null) {
- parent.mInvalidateRegion = new RectF();
- }
- final RectF region = parent.mInvalidateRegion;
- //調(diào)用Animation 的getInvalidateRegion來根據(jù)invalidationTransform計(jì)算 parent的invalidateRegion
- 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;
- //調(diào)用invalidate執(zhí)行下一次繪制請(qǐng)求,這樣動(dòng)畫就動(dòng)起來了
- parent.invalidate(left, top, left + (int) (region.width() + .5f),
- top + (int) (region.height() + .5f));
- }
- }
- 在applyLegacyAnimation方法中會(huì)再次調(diào)用parent.invalidate注冊(cè)一個(gè)Choreographer回調(diào),下一次VSYN后又會(huì)調(diào)用draw方法.這樣就循環(huán)起來了;
- 只有當(dāng)more為false時(shí) 表示動(dòng)畫播放完成了 這時(shí)候就不會(huì)invalidate了;
- 繼續(xù)看getTransformation是如何計(jì)算Transformation的。
- //Animation.java
- //返回值表示動(dòng)畫是否沒有播放完成 并且需要計(jì)算outTransformation 也就是動(dòng)畫需要做的變化
- public boolean getTransformation(long currentTime, Transformation outTransformation) {
- if (mStartTime == -1) {
- mStartTime = currentTime;//記錄第一幀的時(shí)間
- }
- if (duration != 0) {
- normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / //計(jì)算運(yùn)行的進(jìn)度(0-1) (當(dāng)前時(shí)間-開始時(shí)間+偏移量)/動(dòng)畫總時(shí)長(zhǎng)
- (float) duration;
- }
- final boolean expired = normalizedTime >= 1.0f || isCanceled(); //判斷動(dòng)畫是否播放完成 或者被取消
- mMore = !expired;
- if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); //處理最大值
- final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);//根據(jù)插值器計(jì)算的當(dāng)前動(dòng)畫運(yùn)行進(jìn)度
- applyTransformation(interpolatedTime, outTransformation);//根據(jù)動(dòng)畫進(jìn)度 計(jì)算最終的outTransformation
- return mMore;
- }
- 記錄動(dòng)畫第一幀的時(shí)間;
- 根據(jù)當(dāng)前時(shí)間到動(dòng)畫第一幀的時(shí)間這之間的時(shí)長(zhǎng)和動(dòng)畫應(yīng)持續(xù)的時(shí)長(zhǎng)來計(jì)算動(dòng)畫的進(jìn)度;
- 把動(dòng)畫進(jìn)度控制在 0-1 之間,超過 1 的表示動(dòng)畫已經(jīng)結(jié)束,重新賦值為 1 即可;
- 根據(jù)插值器來計(jì)算動(dòng)畫的實(shí)際進(jìn)度;
- 調(diào)用 applyTransformation() 應(yīng)用動(dòng)畫效果。
- //applyTransformation每種類型的動(dòng)畫都有自己的實(shí)現(xiàn) 這里以位移動(dòng)畫為例
- //TranslateAnimation.java
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- //Transformation可以理解成 存儲(chǔ)View的一些變換信息,將變化信息保存到成員變量matrix中
- float dx = mFromXDelta;
- float dy = mFromYDelta;
- if (mFromXDelta != mToXDelta) {
- dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);//計(jì)算X方向需要移動(dòng)的距離
- }
- if (mFromYDelta != mToYDelta) {
- dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);//計(jì)算Y方向需要移動(dòng)的距離
- }
- t.getMatrix().setTranslate(dx, dy); //將最終的結(jié)果設(shè)置到Matrix上面去
- }
- 至此計(jì)算完最終的變化然后應(yīng)用到了Transformation的Matix上,會(huì)在draw方法中拿到該Transformation并應(yīng)用到Canvas上,調(diào)用canvas.concat,進(jìn)行平移動(dòng)畫;
- 當(dāng)動(dòng)畫如果還沒執(zhí)行完,就會(huì)再調(diào)用 invalidate() 方法,層層通知到 ViewRootImpl 再次發(fā)起一次遍歷請(qǐng)求,當(dāng)下一幀屏幕刷新信號(hào)來的時(shí)候;
- 再通過 performTraversals() 遍歷 View 樹繪制時(shí),該 View 的 draw 收到通知被調(diào)用時(shí);
- 會(huì)再次去調(diào)用 applyLegacyAnimation() 方法去執(zhí)行動(dòng)畫相關(guān)操作,包括調(diào)用 getTransformation() 計(jì)算動(dòng)畫進(jìn)度,調(diào)用 applyTransformation() 應(yīng)用動(dòng)畫。
7、動(dòng)畫總結(jié)
- 當(dāng)調(diào)用View.startAnimation(Animation)時(shí),并沒有立即執(zhí)行動(dòng)畫,而是通過invalidate()層層通過到ViewRootImpl發(fā)起一次遍歷View樹的請(qǐng)求,在接收到下一個(gè)(16ms)屏幕信號(hào)刷新時(shí)才發(fā)起遍歷View樹的繪制操作,從DecorView開始遍歷,繪制時(shí)會(huì)調(diào)用draw()方法,如果View有綁定動(dòng)畫則執(zhí)行applyLegacyAnimation()方法處理相關(guān)動(dòng)畫邏輯;
- 在applyLegacyAnimation()里面,先執(zhí)行初始化initialize(),再通知?jiǎng)赢嬮_始o(jì)nAnimationStart(),然后通過getTransformation()計(jì)算動(dòng)畫進(jìn)度,并且它的返回值和動(dòng)畫是否結(jié)束決定是否繼續(xù)通知ViewRootImpl發(fā)起遍歷請(qǐng)求,view樹繪制,如此重復(fù)這個(gè)步驟,并且調(diào)用applyTransformation()方法執(zhí)行動(dòng)畫的邏輯,直到動(dòng)畫結(jié)束。
總結(jié)
至于未來會(huì)怎樣,要走下去才知道,反正路還很長(zhǎng),天總會(huì)亮;
加油老鐵們!
本文轉(zhuǎn)載自微信公眾號(hào)「Android開發(fā)編程」