我們大多都了解Android中view的繪制過程陵究,知道一個view是如何從父view往子view一層層的遞歸下去完成測量、布局和繪制的。
知道這些已經(jīng)能完成基本的簡單的自定義的view的開發(fā)了,但是在實際開發(fā)中我們往往會碰到或者使用兩個同樣很常見的方法—— invalidate和requestLayout.
invalidate
山不過來我就過去尤溜。話不多說,我們看看源碼幼衰。
/**
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} will be called at some point in
* the future.
* <p>
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
*/
public void invalidate() {
invalidate(true);
}
/**
* This is where the invalidate() work actually happens. A full invalidate()
* causes the drawing cache to be invalidated, but this function can be
* called with invalidateCache set to false to skip that invalidation step
* for cases that do not need it (for example, a component that remains at
* the same dimensions with the same content).
*
* @param invalidateCache Whether the drawing cache for this view should be
* invalidated as well. This is usually true for a full
* invalidate, but may be set to false if the View's contents or
* dimensions have not changed.
*/
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
這段代碼還是很簡單靴跛,注釋也說的很清楚,只需要注意的是這個方法只能在主線程中調用渡嚣,invalidate方法最終都是調用了invalidateInternal方法梢睛。
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
...
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();
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// 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);
//往父view傳遞,調用父view的invalidateChild方法
p.invalidateChild(this, damage);
}
...
}
}
從上述代碼中可以發(fā)現(xiàn)识椰,View方法中的invalidateInternal實際上是將刷新區(qū)域往上傳給了父viewGroup的invalidateChild方法绝葡,也就是一個從下往上從子到父的一個回溯過程,在每一層view或者viewGroup中都對自己的顯示區(qū)域和傳過來的刷新的 damage區(qū)域Rect做一個交集腹鹉。我們可以看看ViewGroup中的invalidateChild方法藏畅。
/**
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*/
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
...
if (!childMatrix.isIdentity() ||
(mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
...
transformMatrix.mapRect(boundingRect);
dirty.set((int) (boundingRect.left - 0.5f),
(int) (boundingRect.top - 0.5f),
(int) (boundingRect.right + 0.5f),
(int) (boundingRect.bottom + 0.5f));
}
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
...
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) (boundingRect.left - 0.5f),
(int) (boundingRect.top - 0.5f),
(int) (boundingRect.right + 0.5f),
(int) (boundingRect.bottom + 0.5f));
}
}
} while (parent != null);
}
}
其中最重要的就是第30行,parent = parent.invalidateChildInParent(location, dirty)功咒,
不停地往上遞歸調用invalidateChildInParent方法愉阎,直到頂層view也即是ViewRootImpl.
我們看看ViewGroup的invalidateChildInParent方法。
/**
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*
* This implementation returns null if this ViewGroup does not have a parent,
* if this ViewGroup is already fully invalidated or if the dirty rectangle
* does not intersect with this ViewGroup's bounds.
*/
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
final int left = mLeft;
final int top = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
dirty.setEmpty();
}
}
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
}
return mParent;
} else {
mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
} else {
// in case the dirty rect extends outside the bounds of this container
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
}
return mParent;
}
}
return null;
}
我們會發(fā)現(xiàn)其實這段代碼主要是對傳過來的Rect進行了運算力奋,取了交集榜旦,對damage和自己的顯示區(qū)域,返回的還是parent景殷。
之前也講到了溅呢,在invalidateChild中層層遞歸往父viewGroup回溯,直到ViewRootImpl才會停止猿挚,那我們看看ViewRootImpl中發(fā)生了什么咐旧。
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
...
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
...
invalidateRectOnScreen(dirty);
return null;
}
最關鍵的一步在invalidateRectOnScreen中。
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
...
// Add the new dirty rect to the current one
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
// Intersect with the bounds of the window to skip
// updates that lie outside of the visible region
...
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
這里需要解釋一下的就是绩蜻,invalidateChildInParent中返回的是null铣墨。這個結果在ViewGroup中分析時有用到,就結束了自子view到父view的遞歸過程办绝。
因為invalidateChild中的do-while循環(huán)會終止踏兜。
往下我們看到在ViewRootImpl中調用了scheduleTraversals方法。這一步就是整個invalidate的關鍵執(zhí)行步驟了八秃。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
原來是scheduleTraversals向handler發(fā)送了一個異步消息碱妆,會執(zhí)行TraversalRunnable,這個TraversalRunnable的run方法中昔驱,執(zhí)行的就是
doTraversal方法疹尾。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
performTraversals();
...
}
}
看,doTraversal最后走到了我們熟悉的performTraversals了,performTraversals就是整個View樹開始繪制的起始地方纳本,所以說View調用invalidate方法的實質是層層回溯上傳到父view窍蓝,直到傳遞到ViewRootImpl后調用scheduleTraversals方法,然后整個View樹開始
重新按照Android view的繪制過程分析分析的View繪制流程再來進行view的重繪任務繁成。
postInvalidate
除了常見的invalidate外吓笙,我們還經(jīng)常碰到postInvalidate,其實關于這個的特點我們在一開始有提到過invalidate只能在UI線程進行調用巾腕,所以如果想要在非主線程中進行
invalidate的效果面睛,就需要使用postInvalidate。
/**
* <p>Cause an invalidate to happen on a subsequent cycle through the event loop.
* Use this to invalidate the View from a non-UI thread.</p>
*
* <p>This method can be invoked from outside of the UI thread
* only when this View is attached to a window.</p>
*
* @see #invalidate()
* @see #postInvalidateDelayed(long)
*/
public void postInvalidate() {
postInvalidateDelayed(0);
}
我們可以看到這里其實調用了以下的方法:
/**
* <p>Cause an invalidate to happen on a subsequent cycle through the event
* loop. Waits for the specified amount of time.</p>
*
* <p>This method can be invoked from outside of the UI thread
* only when this View is attached to a window.</p>
*
* @param delayMilliseconds the duration in milliseconds to delay the
* invalidation by
*
* @see #invalidate()
* @see #postInvalidate()
*/
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
可以發(fā)現(xiàn)實際上是調用了ViewRootImpl.dispatchInvalidateDelayed方法尊搬,那么我們來看看這個方法:
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
這里也就是ViewRootImpl的handler發(fā)送了一條消息MSG_INVALIDATE叁鉴,注意這里已經(jīng)涉及到線程間消息傳遞了。
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
...
}
}
到這里為止佛寿,postInvalidate的實質就是在UI Thread中調運了View的invalidate方法幌墓,那接下來View的invalidate方法就不再重復說了,上面已經(jīng)分析過了冀泻。
小結
關于二者的具體流程常侣,上面已經(jīng)寫的很詳細了,對于invalidate弹渔,就是從view開始一層層往上層調用胳施,直到ViewRootImpl,然后重新繪制一遍捞附。
對于postInvalidate,就是在viewRootImpl中給handler發(fā)送了一個請求重繪的消息您没,然后接著走invalidate鸟召,只是這個起始是可以在非UI線程上進行。
需要注意的是氨鹏,invalidate和postInvalidate方法請求重繪View欧募,只會調用draw方法,如果View大小沒有發(fā)生變化就不會再調用layout仆抵,并且只繪制那些需要重繪的View的臟
的Rect跟继,也就是誰調用,重繪誰镣丑。
在平常開發(fā)中舔糖,可能會有以下情況引起view重繪:
- 直接手動調用invalidate方法.請求重新draw,但只會繪制調用者本身的view莺匠。
- 調用setSelection方法金吗。請求重新draw,但只會繪制調用者本身。
- 調用setVisibility方法摇庙。 當View可視狀態(tài)在INVISIBLE轉換VISIBLE時會間接調用invalidate方法旱物,繼而繪制該View。當View的可視狀態(tài)在INVISIBLE\VISIBLE轉換為GONE狀態(tài)時會間接調用requestLayout和invalidate方法卫袒,同時由于View樹大小發(fā)生了變化宵呛,所以會請求measure過程以及l(fā)ayout過程,同樣只繪制需要重新繪制的視圖夕凝。
- 調用setEnabled方法宝穗。請求重新draw,但不會重新繪制任何View包括該調用者本身迹冤。
- 調用requestFocus方法讽营。請求View樹的draw,只繪制需要重繪的View泡徙。
requestLayout
本文最初提到了橱鹏,除了invalidate外,常見以及常用的方法還有requestLayout堪藐。我們來看看這是怎么回事莉兰。
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
* tree. This should not be called while the view hierarchy is currently in a layout
* pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
* end of the current layout pass (and then layout will run again) or after the current
* frame is drawn and the next layout occurs.
*
* <p>Subclasses which override this method should call the superclass method to
* handle possible request-during-layout errors correctly.</p>
*/
@CallSuper
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;
}
}
注意31-33行,是不是和invalidate的思路很像礁竞,也是一層層的回溯調用父view的requestLayout方法糖荒,直至頂級視圖ViewRootImpl。
我們看一下ViewRootImpl的requstLayout方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
checkThread就是檢查一下目標線程是不是當前線程模捂。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
scheduleTraversals做的事情就是和invalidate最后的過程差不多了捶朵,向viewRootImpl的
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
最后也是走到performTraversals了。
因此這些過程和invalidate一樣的狂男。
private void performTraversals() {
...
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
...
boolean insetsChanged = false;
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " + mAttachInfo.mVisibleInsets);
}
if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
insetsChanged = true;
}
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
DisplayMetrics packageMetrics = res.getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
}
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
}
...
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
...
if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null) {
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
...
}
可以看看上述的關鍵代碼综看,由于在ViewRootImpl的requestLayout中設置了mLayoutRequested為true,在一些boolean值的計算后岖食,所以在performTraversal中可以進入走measure和layout红碑,但是從invalidate中進入的performTraversal不會進入measure和layout。
而且由于surface是valid泡垃,所以也不會走到performDraw析珊。
小結
requestLayout方法會層層遞歸到父view中,直至viewRootImpl蔑穴,調用measure過程和layout過程忠寻,不會調用draw過程,也不會重新繪制任何View包括該調用者本身存和。