Android視圖重繪,使用invalidate還是requestLayout
概述
在我們?cè)谶M(jìn)行自定義View的相關(guān)開(kāi)發(fā)中,當(dāng)我們更改了當(dāng)前View的狀態(tài),比如大小,位置等,我們需要重新刷新整個(gè)界面,保證顯示最新的狀態(tài)。在Android中,讓當(dāng)前的視圖重繪有兩種方式,invalidate和requestLayout,今天我們看看這兩種方式的原理以及區(qū)別。
分析
invalidate的原理
public void invalidate() { invalidate(true); }
最后會(huì)調(diào)用到invalidateInternal這個(gè)方法
1 void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, 2 boolean fullInvalidate) { 3 if (mGhostView != null) { 4 mGhostView.invalidate(true); 5 return; 6 } 7 8 if (skipInvalidate()) { 9 return; 10 } 11 12 if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) 13 || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) 14 || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED 15 || (fullInvalidate && isOpaque() != mLastIsOpaque)) { 16 if (fullInvalidate) { 17 mLastIsOpaque = isOpaque(); 18 mPrivateFlags &= ~PFLAG_DRAWN; 19 } 20 21 mPrivateFlags |= PFLAG_DIRTY; 22 23 if (invalidateCache) { 24 mPrivateFlags |= PFLAG_INVALIDATED; 25 mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; 26 } 27 28 // Propagate the damage rectangle to the parent view. 29 final AttachInfo ai = mAttachInfo; 30 final ViewParent p = mParent; 31 if (p != null && ai != null && l < r && t < b) { 32 final Rect damage = ai.mTmpInvalRect; 33 damage.set(l, t, r, b); 34 p.invalidateChild(this, damage); 35 } 36 .....
我們看到方法的最后調(diào)用了ViewParent的invalidateChild方法,因?yàn)閂iewParent是個(gè)接口,invalidateChild是空實(shí)現(xiàn),我們?nèi)タ纯此膶?shí)現(xiàn)類(lèi)ViewRootImpl中的invalidateChild是如何做的
@Override public void invalidateChild(View child, Rect dirty) { invalidateChildInParent(null, dirty); }
1 @Override 2 public ViewParent invalidateChildInParent(int[] location, Rect dirty) { 3 checkThread(); 4 if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); 5 6 if (dirty == null) { 7 invalidate(); 8 return null; 9 } else if (dirty.isEmpty() && !mIsAnimating) { 10 return null; 11 } 12 13 if (mCurScrollY != 0 || mTranslator != null) { 14 mTempRect.set(dirty); 15 dirty = mTempRect; 16 if (mCurScrollY != 0) { 17 dirty.offset(0, -mCurScrollY); 18 } 19 if (mTranslator != null) { 20 mTranslator.translateRectInAppWindowToScreen(dirty); 21 } 22 if (mAttachInfo.mScalingRequired) { 23 dirty.inset(-1, -1); 24 } 25 } 26 27 invalidateRectOnScreen(dirty); 28 29 return null; 30 }
又會(huì)調(diào)用ViewRootImpl中的invalidate方法
void invalidate() { mDirty.set(0, 0, mWidth, mHeight); if (!mWillDrawSoon) { scheduleTraversals(); } }
這里調(diào)用了scheduleTraversals重新開(kāi)始了View的繪制,我們知道View的繪制是從ViewRootImpl的performTraversals方法開(kāi)始的。我們看看scheduleTraversals是不是觸發(fā)了performTraversals。
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
在scheduleTraversals方法中我們發(fā)現(xiàn)了一個(gè)mTraversalRunnable對(duì)象,這個(gè)對(duì)象就是我們要觀察的重點(diǎn)
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
我們發(fā)現(xiàn)這個(gè)對(duì)象就是一個(gè)Runnable對(duì)象,我們?cè)趕cheduleTraversals方法中傳入mTraversalRunnable 就會(huì)執(zhí)行run方法,其中又調(diào)用了doTraversal這個(gè)方法
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
最后我們發(fā)現(xiàn)在doTraversal方法中調(diào)用了performTraversals開(kāi)始了View的重新繪制,這就是invalidate的整個(gè)過(guò)程。
requestLayout的原理
public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view requesting it, // not the views in its parent hierarchy ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } }
其中會(huì)調(diào)用ViewParent的requestLayout方法,同樣,我們?nèi)タ碫iewRootImpl中的requestLayout方法。
Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
這里調(diào)用了scheduleTraversals,后面的步驟就和上面invalidate時(shí)一樣了。相對(duì)來(lái)說(shuō),requestLayout的流程還是比較簡(jiǎn)單的。
區(qū)別
既然兩種方式都可以完成View的重繪,那么有什么區(qū)別呢?
使用invalidate重繪當(dāng)前視圖是不會(huì)再次執(zhí)行measure和layout流程的。因?yàn)橐晥D沒(méi)有強(qiáng)制重新測(cè)量的標(biāo)志位,而且大小也沒(méi)有發(fā)生過(guò)變化,所以這時(shí)只有draw流程可以得到執(zhí)行。
如果你希望視圖的繪制流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而應(yīng)該調(diào)用requestLayout()了

posted on 2018-04-12 15:15 安卓筆記俠 閱讀(3889) 評(píng)論(0) 收藏 舉報(bào)
浙公網(wǎng)安備 33010602011771號(hào)