安卓中invalidate和requestLayout的實現和區別
安卓中invalidate和requestLaoyout都是重新整理View用的,用下面這張圖來表示他們的流程
invalidate和postInvalidate的實現
兩者的區別:invalidate是在主執行緒呼叫,postInvalidate在非主執行緒中呼叫。
首先看postInvalidate的實現
public void postInvalidate() {
postInvalidateDelayed(0);
}
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);
}
}
會呼叫到ViewRootImpl的dispatchInvalidateDelayed方法
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
就是用handler在非主執行緒傳送了一個非同步訊息,接著來看Handler是怎麼處理這個訊息的
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
其中((View) msg.obj)就是最先呼叫postInvalidate的控制元件,也就是現在view在主執行緒實現了呼叫invalidate方法。
我們接著看invalidate方法
public void invalidate() {
invalidate(true);
}
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) {
..........
..........
..........
// 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)這個方法,其中p為ViewParent,可以理解為他的父佈局,我們現在看看ViewGroup中的這個方法
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
..........
..........
..........
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}
// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}
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) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
可以看到這個方法會進入一個do while迴圈,不停地呼叫其ViewParent的invalidateChildInParent方法,直到返回值為空時才結束這個方法。最終的
ViewParent為ViewRootImpl類,他的這個方法實現為
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
..........
..........
..........
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
..........
..........
..........
scheduleTraversals();
}
首先確定他的返回值為null,可知前面的迴圈會結束,而最終會呼叫到scheduleTraversals方法中,這個方法會通過Handler的Runnable傳送一個非同步訊息,最終會呼叫ViewRootImpl的performTraversals()方法執行重繪。
requestLayout的實現
requestLayout的實現相比於invalidate要簡單點
public void requestLayout() {
..........
..........
..........
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
..........
..........
..........
mParent.requestLayout();
..........
..........
..........
}
主要看兩個,一個是給View設定一個PFLAG_FORCE_LAYOUT標記為,一個就是繼續呼叫ViewParent的requestLayout方法,還是直接看到ViewRootImpl的requestLayout方法
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
可以看到最終也是呼叫了scheduleTraversals()方法通知介面重繪。
invalidate和requestLayout的區別
從最上面的圖中我們可以看到invalidate不會經過measure和layout這兩個過程,我們下面分析下為什麼。
我們看下View的measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
..........
..........
..........
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
..........
..........
..........
if (forceLayout || needsLayout) {
..........
..........
..........
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
}
原始碼裡在requestLayout時對View設定了PFLAG_FORCE_LAYOUT標記位,而invalidate則沒有,所以在呼叫invalidate重繪時forceLayout 為false,也就是最上層的控制元件就不會呼叫onMeasure方法,那麼下面的控制元件也肯定不會進行重新測量。而重新測量的控制元件又會被標記上PFLAG_LAYOUT_REQUIRED,下面看layout方法
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);
}
..........
..........
..........
}
可以看到如果要呼叫onLayout方法,要麼位置發生了變化,changed為true,要PFLAG_LAYOUT_REQUIRED標記位,所以invalidate的重繪頂層控制元件也不會呼叫onLayout方法,這也就解釋了為什麼最上面的圖裡,invalidate不會經過measure和layout這兩個過程。
在我看來invalidate應該是在控制元件內容或是可見性發生了變化,而其大小位置等不會發生變化時呼叫,所以自然也不需要重新測量佈局,而requestLayout則是大小位置發生了變化則呼叫,使用場景有著區別。