上篇文章View 的測量分析了 View 的工作原理中最復雜的測量過程夺荒,接著測量過程的是布局和繪制的過程岩臣,這里兩個過程相對比較簡單,所以放到一篇文章中來寫
View 的測量過程中,確定了 View 的測量寬高的信息闷板,布局過程則是為了確定 View 在其父 View 中的位置以及 ViewGroup 確定其所有子 View 元素的位置;布局結束后會執(zhí)行繪制過程院塞,繪制過程將 View 需要顯示的內容繪制到屏幕上
一遮晚、layout (布局)過程
依舊從 ViewRootImpl 的 performLayout 方法開始,其中調用 DecorView 的 layout 方法,layout 方法是在 View 類中定義和實現(xiàn)的拦止,其中 getMeasuredWidth县遣、getMeasuredHeight 方法得到的是 DecorView 在測量過程中確定的測量寬高
// ViewRootImpl
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// View
public void layout(int l, int t, int r, int b) {
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;
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);
...
}
...
}
// View
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
...
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
...
}
layout 方法中調用 setFrame 方法,可以看到 setFrame 方法中保存了 View 四個頂點在父 View 中的位置汹族,四個頂點確定萧求,View在父容器中的位置也就確定,View 的實際寬高也就確定顶瞒,接著就會調用 onLayout 方法
View 的 onLayout 方法是個空實現(xiàn)夸政,說明 View 在布局過程中的任務就是確定自己在父 View 中的位置,確定了在父 View 中的位置后也就確定了子 View 的最終寬高榴徐,如果不重寫 View 的 layout 方法守问,其最終寬高與測量寬高相等匀归,如果重寫了 layout 方法,并且子 View 的位置不以測量寬高來確定酪碘,此時 View 的最終寬高將不等于測量寬高朋譬。測量寬高賦值于測量過程,最終寬高賦值于布局過程兴垦,兩者賦值時機不同
ViewGroup 中 onLayout 是個抽象方法徙赢,其子類必須重寫該方法以確定其所有子 View 的位置,下面重點分析 ViewGroup 的 onLayout 方法
1探越、ViewGroup 的 onLayout 方法
由于 ViewGroup 的 onLayout 方法是個抽象方法狡赐,所以我們選一個特定的 ViewGroup 實現(xiàn)類來分析,這里分析 LinearLayout 的 onLayout() 方法
// LinearLayout
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
// LinearLayout
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount(); // 子 View 數(shù)量
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
// 遍歷所有子 View
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
// 獲取子 View 由測量過程確定的的測量寬高
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
// 確定子 View 的 left 位置
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin; // 確定子 View 的 top 位置
//
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
// 確定下一子 View 的 top 位置
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
// LinearLayout
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
LinearLayout 的 onLayout 方法中根據(jù)設置的 View 線性排列的方向確定如果實現(xiàn)布局钦幔,以豎直排列的 情況為例枕屉,會調用 layoutVertical 方法
layoutVertical 中會遍歷所有的子 View,由子 View 的測量寬高和 ViewGroup 自身業(yè)務邏輯確定子 View 的四個頂點在 ViewGroup 中的位置鲤氢,并通過 setChildFrame 方法來調用子 View 的 layout 方法搀擂。從而將布局過程從 ViewGroup 傳遞到 View 中,View 中的 layout 方法上面已經分析了卷玉,作用為確定自己四個頂點在父 View 中的位置哨颂。這樣一層一層傳遞下去就完成了 View 視圖樹的 layout 過程。
1. ViewGroup 的布局過程的作用為先確定自己在父容器的位置相种,再確定子 View 在該 ViewGroup 中的位置威恼,子 View 的 layout 結果不會影響 ViewGroup 的layout
2. view 的布局過程的作用為確定自己四個頂點在父 View 中的位置
2、View 的 getMeasuredWidth() 和 getWidth() 的區(qū)別
子元素的layout() 方法中會根據(jù)父容器中傳遞的頂點位置為 mLeft , mTop , mRight , mBottom 等屬性賦值寝并,View 的 getWidth() 方法得到的值為 mRight - mLeft
View 的 getMeasuredWidth() 方法得到的值是 View 的 mMeasuredWidth 參數(shù)的值箫措,該參數(shù)的賦值是在 onMeasure() 方法中
這兩個方法得到的值不是同一個參數(shù)的值,兩個參數(shù)的賦值時間是不同的衬潦,如果View 重寫 layout 方法,修改四個頂點的位置斤蔓,這樣兩個方法得到的值就是不同的
所以不能說這兩值一定相等。getWidth() 方法得到的是 View 的最終寬高镀岛,getMeasuredWidth() 方法得到的是 View 的測量寬高
- getHeight() 和 getMeasuredHeight() 方法同理弦牡。
二、View 的 draw() 繪制過程
測量和布局過程完成之后哎媚,ViewRootImpl 會接著調用 performDraw 方法喇伯,該方法最終會調用 DecorView 的 draw() 方法
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);
// 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;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// 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);
}
// View
protected void onDraw(Canvas canvas) {}
// ViewGroup
protected void dispatchDraw(Canvas canvas) {
for (int i = 0; i < childrenCount; i++) {
...
drawChild(canvas, transientChild, drawingTime);
...
}
}
// ViewGroup
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
由 View 的 draw 方法可分析得出以下繪制流程
1. View 繪制的流程
- 繪制背景 background.draw(canvas)
- 保存 canvas 圖層
- 繪制自己 調用 onDraw(canvas) 方法
- 繪制 children (dispatchDraw)
- 繪制漸變效果和恢復 canvas 圖層
- 繪制裝飾 (onDrawScrollBars)
onDraw() 為空實現(xiàn)喊儡,需要子類根據(jù)需要顯示的內容重寫此方法
dispatchDraw() 方法也是空實現(xiàn)拨与,ViewGroup 中重寫了此方法,dispatchDraw 方法中遍歷所有子 View艾猜,并調用其 draw() 方法买喧,將繪制過程一層層傳遞捻悯,完成了 View 樹的繪制過程。
2. setWillNotDraw() 方法
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
setWillNotDraw() 方法的作用可以從其注釋中看出淤毛,如果一個 View 不需要繪制任何內容今缚,那么設置這個標記位 true 后,系統(tǒng)會進行相應的優(yōu)化低淡。
默認情況下 View 沒有開啟這個標記姓言,而 ViewGroup 則開啟了這個標記。
在開發(fā)過程中蔗蹋,如果我們的 View 繼承自 ViewGroup 且沒有進行繪制時就可以開啟這個標記以便于系統(tǒng)對其進行優(yōu)化何荚,如果該 ViewGroup 需要通過 onDraw 來繪制內容,則需要通過調用 setWillNotDraw() 方法來關閉此標記
好啦猪杭,到這里 View 的工作過程中測量餐塘、布局、繪制三大過程的分析就結束啦皂吮,接下來將是 View 的事件分發(fā)機制和自定義 View 的文章戒傻,敬請期待