ViewRootImpl
Measure
Layout
Draw
入口函數:performTraversals
Android 28源碼:
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec)
Measure
MeasureSpec: 高2位(mode)低30位(size)
onMeasure和measure的區別
Measure不可以被重寫 負責進行測量的傳遞
onMeasure可以被重寫,負責測量的具體實現
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
在這種情況下,performMeasure會調用兩次
測量結束之后,開始進行布局
Layout
performLayout(lp, mWidth, mHeight);
mView.layout(0,0,mView,getMeasuredWidth(),mView.getMeasuredHeight)
特殊情況:如果在layout的過程中,還有其他的layout請求過來,那么就需要清除所有的標志位然后做一整套的請求,測量,布局去處理這個情況
View.layout->View.onLayout(changed, l ,t , r ,b)
View 當中的onLayout是一個空實現,可以提供給子類去重寫
View的layout與measure不同,它是一個public的方法且不是final,可以由子類繼承去重寫
那我們看下ViewGroup的實現
在ViewGroup當中,layout是被final修飾,且也繼承了View的這個方法的實現,也就是說,我們通過實現onLayout方法就可以指定我們自己的類的布局方式,因為layout中會調用到onLayout函數
在ViewGroup當中,onLayout被abstract修飾,證明ViewGroup的子類一定要去自己實現onLayout來進行具體的布局
對于View的子類和ViewGroup的子類我們分別來看一個具體的例子:
1.TextView
在TextView當中沒有去重寫layout,只是簡單的重寫了onLayout方法
2.RelativeLayout
ViewGroup的子類沒辦法重寫layout函數,在onLayout中,RelativeLayout將子View通過循環遍歷的方式取出,然后去讓子View調用自己的layout方式去進行布局
onLayout的職責:它并不負責自己的布局,它的布局都是由它的爸爸去出發,它只負責自己子view 的布局調用
Draw
performDraw();
boolean canUseAsync = draw(fullRedrawNeeded);
drawSoftware(surface,mAttachInfo,xOffset,yOffset,scalingRequired, dirty, surfaceInsets)
canvas = mSurface.lockCanvas(dirty);
mView.draw(canvas)
onDraw(cavas){空實現,對自己進行繪制,需要子類自己去實現}
dispatchDraw(canvas);{空實現,對孩子進行繪制,需要子類自己去實現}
我們分別從View 和 ViewGroup的角度看下這相關的三個函數
Draw:傳遞數據
onDraw:繪制自身
dispatchDraw:繪制子View
View:
Draw 是public且不是fianl類型,子類可以繼承去進行重寫
onDraw是空實現,子類可以重寫也可以不重寫
dispatchDraw是空實現,子類可以重寫也可以不重寫
ViewGroup:
沒有重寫draw以及onDraw方法
重寫了dispatchDraw方法
drawChild(canvas, child, drawingTime);
child.draw(canvas, this, drawingTime);
在ViewGroup中沒有對三個函數做特殊限制也就是說,任何ViewGroup的子類都可以重寫三個函數,或者直接使用這三個函數
RelativeLayout:
沒有重寫父類的三個方法
TextView:
重寫了View的onDraw方法
requestLayout 和 invalidate 詳解
View.requestLayout:
官方解釋:
當視圖的布局有改變的時候,刷新
它將會在視圖樹上遍歷執行一邊layout
這個不應該在視圖層級正在layout的時候調用
如果layout操作正在執行,這個請求會被接受在當前的layout操作結束的時候,layout會被再次調用
或者在當前的幀被繪制完成然后下一次的layout操作發生的時候
子類如果想要重寫這個方法的話,應該先調用父類的函數實現去正確的處理可能會發生的一些 布局期間請求的 錯誤。
1.如果測量緩存不為空,將測量緩存清空??
2.如果當前的根布局在布局過程中,return函數
3.如果爸爸不為空,而且爸爸已經布局結束,那么就調用爸爸的requestLayout
ViewGroup沒有重寫View的requestLayout方法。
重點:
這里有兩個標志位的設置
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
也就是說這個是從子節點往根節點的逆序遍歷調用過程
最終調用到的就是DecorView ,那么我們看下DecorView的爸爸是誰
view.assignParent(this);
那么就是說,DecorView的爸爸就是ViewRootImpl
ViewRootImpl.requestLayout
如果沒有在處理layout請求的過程中
1.檢查是不是主線程
2.將layout請求的flag設置為true
3.調用scheduleTraversals
1.mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
performTraversals();
又重新調回了performTrarsals,重新開始了 measure/layout/draw整個流程
那么是不是所有的子View都需要測量呢??這時候標志位就派上用場了
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
…
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
所以只有調用了requestlayout的View才會進行重新測量
在View.layout中,在調用了onLayout之后有一個這樣的代碼
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
標志位已經恢復
在進行完layout之后,標志位復原
在draw函數中,
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
重點看這個dirtyOpaque
if (!dirtyOpaque) {
drawBackground(canvas);
}
if (!dirtyOpaque) onDraw(canvas);
dirtyOpaque == TRUE的時候,不會調用自身的繪制方法,子View也雷同
所以requestLayout
1.由子View調用爸爸的requestLayout,一直到ViewRootImpl為止,它的requestLayout又調用到了performTraversals函數,開始了 measure,layout,draw由于在layout的時候,標志位恢復,導致draw就不會被執行,也就是說requestLayout就到layout函數為止就結束了。
invalidate()
官方解釋:
重新繪制全部的View,如果這個View是可見的,onDraw就會在未來的某個點被調用,這個函數一定要從UI線程調用,如果在非UI線程調用的話,調用postInvalidate方法
invalidate(true);
public void invalidate(boolean invalidateCache) {
官方解釋:
這個函數是invalidate實際開始進行工作的地方一個完整的invalidate函數調用造成視圖緩存被重繪,但是這個方法可以通過高設置
invalidateCache參數為false跳過這些重繪的步驟對于那些不需要的情況,例如一個組件的內容和大小都沒變化
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
1.如果需要跳過重繪,跳過
return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
(!(mParent instanceof ViewGroup) ||
!((ViewGroup) mParent).isViewTransitioning(this));
2.p.invalidateChild(this, damage);
ViewGroup.invalidateChild(@Deprecated) instead of onDescendantInvalidated
// HW accelerated fast path
onDescendantInvalidated(child, child);
if (mParent != null) {
mParent.onDescendantInvalidated(this, target);
}
So,這個方法也最后會調用到最后一個爸爸的onDescendantInvalidated這個方法,那我們直接到ViewRootImpl去看下
@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
mIsAnimating = true;
}
invalidate();
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
Ok,看到這里優勢很熟悉的代碼啦,
scheduleTraversals這個函數最后仍然是調用到了performTraversals,請求重繪View的樹,即draw過程,假如視圖沒有發生大小的變化的話就不回進行layout的過程,只是繪制需要繪制的那些視圖
mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
是否執行onMeasure方法也和這個標志位有關系mPrivateFlags
是否執行onLayout也和這個標志位有關系mPrivateFlags
所以mPrivateFlags的變化是控制整個繪制流程的一個重點?。。。。。。。。?/p>
1 invalidate,請求重新draw,只會繪制調用者本身。
2 setSelected 也是請求invalidate,同上
*/
public void setSelected(boolean selected) {
//noinspection DoubleNegation
invalidate(true);
}
}
3.setVisibility
當View從INVISIBLE變為VISIBILE,會間接調用invalidate方法,繼而繪制該View,而從INVISIBLE/VISIBLE變為GONE之后,由于View樹的大小發生了變化,會進行measure/layout/draw,同樣,他只會繪制需要重繪的視圖。
if (newVisibility == VISIBLE) {
invalidate(true);
}
/* Check if the GONE bit has changed */
if ((changed & GONE) != 0) {
requestLayout();
((View) mParent).invalidate(true);
}
if ((changed & INVISIBLE) != 0) {
/*
If this view is becoming invisible, set the DRAWN flag so that
the next invalidate() will not be skipped.
*/
mPrivateFlags |= PFLAG_DRAWN;
}
setEnable:請求重新draw,只會繪制調用者本身。invalidate(true);
requestFocus:請求重新draw,只會繪制需要重繪的視圖。
補充學習:
1.http://blog.csdn.net/yanbober/article/details/46128379/
2.http://blog.csdn.net/a553181867/article/details/51583060
View中 getWidth 和 getMeasuredWidth 的區別
先看下getWidth:
/**
- Return the width of your view.*
*** @return The width of your view, in pixels.
- /
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
返回你視圖的寬度,以像素為單位
這些坐標值是從onLayout中傳遞過來
有他的右邊的坐標減去它的左邊的坐標得出
getMeasuredWidth
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}測量階段中計算出的寬度。
返回未經加工的測量的寬度
源頭是從onMeasure傳遞進來
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
- getMeasuredWidth是measure階段獲得的View的原始寬度。
- getWidth是layout階段完成后,其在父容器中所占的最終寬度
舉一個例子
我們自定義一個lineaerlayout
不復寫它的onMeasure方法,那么這里的測量結果一定是我們xml設置的值
在layout函數中我們在傳入坐標的地方做一些手腳,那么最終顯示是以getWidth的數據為準
child.layout(child.getLeft() ,child.getTop(), child.getRight() + 400, child.getBottom());