- DecorView 是界面上的頂層view. ViewRoot是連接WindowManager和DecorView的紐帶.view的繪制流程從ViewRoot的perfromTraversals開始.
- ViewRoot作為頂級View,通常包括一個豎直方向的LinerLayout.這個LinerLayout分兩部分.上邊是標(biāo)題欄.就是屏幕顯示時間的那一行,下邊是內(nèi)容欄.id為content,我們平時設(shè)置的setContentView(),就是在想contentView里添加子類.通過ViewGroup content =(ViewGroup)findViewById(android.R.id.content);拿到contentView. content.getChildAt(0);拿到我們設(shè)置的View;
- MeasureSpace 用一個32位的int代表測量的數(shù)據(jù)和類型,高二位代表SpecMode,表示測量模式,低30位 SpecSize表示測量大小.
- MeasureSpace的三種模式
- UNSPECIFIED:父容器不做限制,通常用于系統(tǒng)內(nèi)部.
- EXACTLY 指定大小,view此時的大小就是SpecSize的值.它對應(yīng)于LayoutParams中的march_parent和具體指定的值(多少dp,sp).
- At_MOST 父容器指定一個可用大小即SpecSize,View不能大于這個值.對于wrap_content.
- MeasureSpace 決定view的寬高,但MeasureSpace由父類和view自己的layoutParams共同決定.
- DecorView的MeasureSpace決定方式
-接著看vp如何測設(shè)計子類的MeasureSpace
//parentWidthMeasureSpec,parentHeightMeasureSpec 是vp自己的measureSpace寬高值
//widthUsed,heightUsed 是被vp額外用掉的寬高值
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//viewgroup自己的measureSpace,第二個參數(shù)是vp所使用的值
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//調(diào)用子類進(jìn)行測量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//得到子類的MeasureSpace 通過父類的specMode和子類Layoutparams的組合得到子類最終的MeasureSpace
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
vp的MeasureSpec
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//vp自己的尺寸減去自己的padding.margin等 最后得到子類能用的最大尺寸
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY: //vp是精確模式
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST: //父類是最大模式
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED: //父類是不確定模式
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
-最終的匹配結(jié)果如下,parentSize值最終父類可用大小,既getChildMeasureSpec中的 size
-measure 過程
-view 的measures過程
view 的measure方法是final,既子類無法重寫該方法,在measure 中又調(diào)用了onMeasure方法,只需要看onMeasure方法就可以.
//widthMeasureSpec,heightMeasureSpec為父類vp傳過來的子類的MeasureSpace 寬高值
//getSuggestedMinimumWidth() 是建議的最小寬度,一般為view背景的大小和屬性android:minWidth大小的最大值.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//子類可以重寫這個方法來決定view的measure大小.但是必須調(diào)用setMeasuredDimension()方法,不然會拋出異常.
setMeasuredDimension()方法會設(shè)置view寬高的測量值.
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
//UNSPECIFIED的情況可以忽略.看剩下兩種情況.所以 getDefaultSize的取值其實就是vp傳過來的measureSpace的specSize的值.
- 經(jīng)過以上代碼我們得出結(jié)論:直接繼承View的自定義空間要重寫onMeasure方法并設(shè)置wrap_content時自身大小,不然設(shè)置wrap_content和設(shè)置march_parent的效果一樣.
- 因為view 設(shè)置為wrap_content, 對應(yīng)的MeasureSpec 為SpecMode=AT_MOST,SpecSize=parentSize,在onMeasure中就會把父類可用空間的值設(shè)置給view.解決辦法如下
-ViewGroup 的measures過程
-
viewgroup 出來測量自己,還要遍歷子veiw測量大小.vp通過measureChildren方法來遍歷測量子類大小
//Gone的子view不會被測量 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } //這個方法和之前的 measureChildWithMarging 方法差不多,只是少了widthUsed,heighUsed參數(shù) protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
viewgroup并沒有實現(xiàn)具體測量過程,因為他是一個抽象類,需要子類例如LinerLayout等自行實現(xiàn)測量過程.
-
四種方法拿到view的寬高
- Activity/View#onWindowFocusChanged,這個方法的含義標(biāo)識窗口的交點變化時回調(diào),此時view已經(jīng)初始化完畢,寬高已經(jīng)準(zhǔn)備好了.此方法會回調(diào)多次
public void onWindowFocusChanged(boolean hasFocus){ super.onWindowFocusChanged(hasFocus); if(hasFocus){ int width=view.getMeasuredWidth(); int hei=view.getMeasuredHeigh(); } }
-
view.post(runnable) 發(fā)送一個消息到消息隊列的尾部,等Lopper調(diào)用此消息時,view已經(jīng)初始化完成
view.post(new Runnable{
@Override public void run(){ int width=view.getMeasuredWidth(); int hei=view.getMeasuredHeigh(); }
});
-
ViewTreeObserver#onGlobalLayoutListener,當(dāng)view樹的狀態(tài)或者view樹的子view可見性發(fā)生改變時會回調(diào),方法會被回調(diào)多次
ViewTreeObserver observer=view.getViewTreeObserver();
observer.addonGlobalLayoutListener(new onGlobalLayoutListener(){publci void onGlobalLayout(){ view.getViewTreeObserver().removeGlobalLayoutListener(this);//先移除監(jiān)聽 int width=view.getMeasuredWidth(); int hei=view.getMeasuredHeigh(); }
});
-
view.measure(int,int);需要根據(jù)view 的layoutParam 來區(qū)分
march_parent :此狀態(tài)無法得到結(jié)果,因為此時父view的測量寬高也不知道
具體數(shù)值 :比如寬高為100int width =MeasureSpec.makeMeasureSpec(100,MeasursSpec.EACTLY) int heigh =MeasureSpec.makeMeasureSpec(100,MeasursSpec.EACTLY) view.measurd(width,heigh);
wrap_content
int width =MeasureSpec.makeMeasureSpec((1<<30)-1,MeasursSpec.EACTLY) int heigh =MeasureSpec.makeMeasureSpec((1<<30)-1,MeasursSpec.EACTLY) view.measurd(width,heigh);
View 的最大尺寸為30位二進(jìn)制,既 2的30次方減1 既(1<<30)-1,在最大化模式下,我們用view理論上的最大值去構(gòu)造MeasureSpec是合理的.
-layout 過程
layout 方法確定view本身的位置,在onLayout方法則會確定所有子元素的位置.
viewGroup 在位置被確定后,會在onLayout中便利所有子類比調(diào)用其layout方法,在layout方法中onLayout方法又被調(diào)用.
view 的layout方法中, 通過setFrame(l,t,r,b)來設(shè)定view的四個定點的位置.父容器通過onLayout方法來確定子view的位置.
view和viewgroup均沒實現(xiàn)onLayout方法,需要看具體的layout.
-
view 的layout過程
public void layout(int l, int t, int r, int b) { //如果需要在layout前進(jìn)行一次measure if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } //之前的位置 左上右下 int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; //主要部分,設(shè)置view的左上右下位置. boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //通知監(jiān)聽器,界面layout發(fā)生變化 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); //空實現(xiàn) mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }
-vieGroup layout過程
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b); //其實還是調(diào)用了父類,最終調(diào)用view 的layout
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
getwidth getMeasuredWidth 區(qū)別
getMeasuredWith 形成于measure過程.getWidth 形成于layout過程.
-
一般情況兩者是相同的.除非如下情況
public void layout (int l,int t,int r,int b){ super.layout(l,t,r+100,b+100); }
-draw 過程
1罐柳、對View的背景進(jìn)行繪制
2、保存當(dāng)前的圖層信息(可跳過)
3老客、繪制View的內(nèi)容
4、對View的子View進(jìn)行繪制(如果有子View)
5、繪制View的褪色的邊緣蛛壳,類似于陰影效果(可跳過)
6吸占、繪制View的裝飾(例如:滾動條)
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas); //繪制背景
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas); //空實現(xiàn),需要子類實現(xiàn)
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
...
}
分布講解
1繪制背景
private void drawBackground(Canvas canvas) {
//mBackground是該View的背景參數(shù)馁筐,比如背景顏色,沒有背景就不繪制
final Drawable background = mBackground;
if (background == null) {
return;
}
//根據(jù)View四個布局參數(shù)來確定背景的邊界 mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
setBackgroundBounds();
...
//獲取當(dāng)前View的mScrollX和mScrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
//如果scrollX和scrollY有值调限,則對canvas的坐標(biāo)進(jìn)行偏移舟陆,再繪制背景
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
3.繪制子View,view中這個方法為空,viewgroup實現(xiàn)了這個方法
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime); //這句是重點
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);//這句是重點
}
}
//省略...
}
drawChild(canvas, transientChild, drawingTime)的實現(xiàn),調(diào)用了子view的繪制方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
- view 的draw(Canvas canvas, ViewGroup parent, long drawingTime)
- 我們主要來看核心部分,首先判斷是否已經(jīng)有緩存旧噪,即之前是否已經(jīng)繪制過一次了吨娜,如果沒有脓匿,則會調(diào)用draw(canvas)方法淘钟,開始正常的繪制,即上面所說的六個步驟陪毡,否則利用緩存來顯示米母。
- 這一步也可以歸納為ViewGroup繪制過程,它對子View進(jìn)行了繪制毡琉,而子View又會調(diào)用自身的draw方法來繪制自身铁瞒,這樣不斷遍歷子View及子View的不斷對自身的繪制,從而使得View樹完成繪制
Skip 6 繪制裝飾,指View除了背景桅滋、內(nèi)容慧耍、子View的其余部分,例如滾動條等丐谋,我們看View#onDrawForeground: public void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas); onDrawScrollBars(canvas); final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; if (foreground != null) { if (mForegroundInfo.mBoundsChanged) { mForegroundInfo.mBoundsChanged = false; final Rect selfBounds = mForegroundInfo.mSelfBounds; final Rect overlayBounds = mForegroundInfo.mOverlayBounds; if (mForegroundInfo.mInsidePadding) { selfBounds.set(0, 0, getWidth(), getHeight()); } else { selfBounds.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); } final int ld = getLayoutDirection(); Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); foreground.setBounds(overlayBounds); } foreground.draw(canvas); } }