一、requestLayout和invalidate的區別
requestLayout() 和 invalidate() 的區別在于它們作用的范圍不同。requestLayout() 用于通知 View 進行重新布局,即測量、布局和繪制三個步驟都會重新進行;而 invalidate() 用于通知 View 進行重繪,僅僅是在原有的尺寸和位置上重新繪制 View,不會重新進行測量和布局。
requestLayout:調用 View.requestLayout 方法后會依次調用 performMeasure, performLayout 和 performDraw 方法,調用者 View 及其父 View 會重新從上往下進行 measure, layout 流程,一般情況下不會執行 draw 流程(子 View 會通過判斷其尺寸/頂點是否發生改變而決定是否重新 measure/layout/draw 流程)。因此,當只需要進行重繪時可以使用 invalidate 方法,如果需要重新測量和布局則可以使用 requestLayout 方法,而 requestLayout 方法不一定會重繪,因此如果要進行重繪可以再手動調用 invalidate 方法。invalidate:調用 View.invalidate() 方法后會逐級往上調用父 View 的相關方法,最終在 Choreographer 的控制下調用 ViewRootImpl.performTraversals() 方法。只有滿足 mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null … 等條件才會執行 measure 和 layout 流程,否則只執行 draw 流程,draw 流程的執行過程與是否開啟硬件加速有關:關閉硬件加速則從 DecorView 開始往下的所有子 View 都會被重新繪制。開啟硬件加速則只有調用 invalidate 方法的 View 才會重新繪制。二、requestLayout方法介紹
1、View.requestLayout
Java復制代碼public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // 如果處于 Layout 則將該請求加入 ViewRootImpl 中的任務隊列中 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; }}// ViewRootImplboolean requestLayoutDuringLayout(final View view) { if (!mLayoutRequesters.contains(view)) { mLayoutRequesters.add(view); } // ...}
如果此時處于 Layout 則將該請求加入 ViewRootImpl 中的任務隊列中,否則向上調用父 View 的 requestLayout 方法,直到 ViewRootImpl 中:
Java復制代碼public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}
ViewRootImpl.requestLayout 方法在 check 了線程后將 mLayoutRequested 置為 true 且調用 scheduleTraversals 方法,于是在 Vsync 信號到來后會調用 performTraversals 方法。由于 mLayoutRequested == true,因此會依次執行 performMeasure, performLayout 以及 performDraw 方法開始 View 的繪制流程。
2、繪制過程
measure:
接下來看看 View.requestLayout 方法對整個 View 樹的影響。首先看一下 View.measure 方法:
Java復制代碼public final void measure(int widthMeasureSpec, int heightMeasureSpec) { // ... final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec; final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); if (forceLayout || needsLayout) { // ... onMeasure(widthMeasureSpec, heightMeasureSpec); // 設置 PFLAG_LAYOUT_REQUIRED 標志位 mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; }}
在 View.requestLayout 方法中已經看到給當前 View 及其父 View 都添加了 PFLAG_FORCE_LAYOUT 標志位,因此其 forceLayout == ture,即會執行 onMeasure 方法測量。而對于未設置 PFLAG_FORCE_LAYOUT 標志位的 View 則需要判斷其尺寸是否發生改變才會決定調用 onMeasure 與否。我們看到調用 onMeasure 后又設置了 PFLAG_LAYOUT_REQUIRED 標志位。
layout:
接著看 View.layout 方法:
Java復制代碼public void layout(int l, int t, int r, int b) { boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); // ... } // ...}
由于調用 onMeasure 后設置了 PFLAG_LAYOUT_REQUIRED 標志位,因此也會跟著執行 onLayout 方法。另外看一下 setOpticalFrame 和 setFrame 方法,其中 setOpticalFrame 方法中最終也會調用到 setFrame 方法:
Java復制代碼protected boolean setFrame(int left, int 較好, int right, int bottom) { boolean changed = false; if (mLeft != left || mRight != right || mTop != 較好 || mBottom != bottom) { int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - 較好; boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); invalidate(sizeChanged); // ... }}
因此可以看到當 View 四個頂點發生變化時也會調用 onLayout 方法,且會調用 View.invalidate 方法,并將 View 的寬高是否發生變化傳給 invalidateCache 參數。
draw:
ViewRootImpl.performDraw 會調用到 ViewRootImpl.draw 方法:
Java復制代碼private boolean draw(boolean fullRedrawNeeded) { final Rect dirty = mDirty; if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { // 硬件繪制/軟件繪制 }}
dirty 是臟區,在 ViewRootImpl.invalidate 方法中會調用 mDirty.set() 方法為其設置邊界值,如果上面 View 的頂點沒有發生變化則不會調用 invalidate 方法,則 dirty.isEmpty()
返回 true,因此整個 View 樹都不會重繪。
3、小結
調用 View.requestLayout 方法后會依次調用 performMeasure, performLayout 和 performDraw 方法,調用者 View 及其父 View 會從上往下重新進行 measure, layout 流程,一般情況下不會執行 draw 流程(子 View 會通過判斷其尺寸/頂點是否發生改變而決定是否重新 measure/layout/draw 流程)。
三、invalidate方法介紹
1、View.invalidate
先看一下 invalidate 這個方法:
Java復制代碼public void invalidate() { invalidate(true);}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 (skipInvalidate()) { // 判斷是否需要跳過 invalidate return; } if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || (fullInvalidate && isOpaque() != mLastIsOpaque)) { // 判斷是否重繪 if (fullInvalidate) { mLastIsOpaque = isOpaque(); // 重新設置 Opaque mPrivateFlags &= ~PFLAG_DRAWN; // 移除 PFLAG_DRAWN 標志位 } mPrivateFlags |= PFLAG_DIRTY; // 設置 PFLAG_DIRTY 臟區標志位 if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; // 設置 PFLAG_INVALIDATED 標志位 mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; // 移除 PFLAG_DRAWING_CACHE_VALID 標志位 } final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { // damage 表示要重繪的臟區 final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); p.invalidateChild(this, damage); } // ... }}private boolean skipInvalidate() { return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null && (!(mParent instanceof ViewGroup) || !((ViewGroup) mParent).isViewTransitioning(this));}
首先會通過 skipInvalidate 方法判斷是否要跳過 invalidate 過程,如果同時滿足以下條件則跳過:
View 不可見當前沒有運行動畫父 View 不是 ViewGroup 類型或者父 ViewGoup 不處于過渡態接下來再判斷是否需要重繪,如果滿足以下任意一個條件則進行重繪:
View 已經繪制完成且具有邊界invalidateCache == true 且設置了 PFLAG_DRAWING_CACHE_VALID 標志位,即繪制緩存可用沒有設置 PFLAG_INVALIDATED 標志位,即沒有被重繪過fullInvalidate == true 且在 透明 和 不透明 之間發生了變化在處理了一些標志位的邏輯后調用了父 View 的 invalidateChild 方法并將要重繪的區域 damage 傳給父 View。于是接著看 ViewGroup.invalidateChild 方法:
Java復制代碼public final void invalidateChild(View child, final Rect dirty) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null && attachInfo.mHardwareAccelerated) { // 開啟了硬件加速 onDescendantInvalidated(child, child); return; } // 未開啟硬件加速 ViewParent parent = this; if (attachInfo != null) { // ... do { // ... parent = parent.invalidateChildInParent(location, dirty); // 重新設置臟區 // ... } while (parent != null); }}
可以看到這里會根據是否開啟了硬件加速而走不同的邏輯。
2、小結
調用 View.invalidate() 方法后會逐級往上調用父 View 的相關方法,最終在 Choreographer 的控制下調用 ViewRootImpl.performTraversals() 方法。由于 mLayoutRequested == false,因此只有滿足 mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null … 等條件才會執行 measure 和 layout 流程,否則只執行 draw 流程,draw 流程的執行過程與是否開啟硬件加速有關:
關閉硬件加速則從 DecorView 開始往下的所有子 View 都會被重新繪制。開啟硬件加速則只有調用 invalidate 方法的 View 才會重新繪制。View 在繪制后會設置 PFLAG_DRAWN 標志位。
延伸閱讀1:Android中的View類簡介
Android中的View類代表用戶界面中基本的構建塊。一個View在屏幕中占據一個矩形區域、并且負責繪制和事件處理。View是所有widgets的基礎類,widgets是我們通常用于創建和用戶交互的組件,比如按鈕、文本輸入框等等。子類ViewGroup是所有布局(layout)的基礎類。layout是一個不看見的容器,里面堆放著其他的view或者ViewGroup,并且設置他們的布局屬性。