這里介紹 View 的 Layout 和 Draw 過程嘿歌。
Layout 過程
Layout 過程的作用是 ViewGroup 來確定子元素的位置掸掏,來看 View 的 layout 方法
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);
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;
}
首先通過 setFrame 方法來設(shè)定 View 四個頂點(diǎn)位置,分別是 mLeft宙帝、mRight丧凤、mTop、mBottom 的值步脓,四個頂點(diǎn)一旦確定愿待,View 在父容器的位置也就確定了。接著調(diào)用 onLayout 方法靴患,它用來確定子元素的位置仍侥。與 onMeasure 方法類似,View 和 ViewGroup 都沒有實現(xiàn) onLayout 方法鸳君,于是再次選擇 LinearLayout 的 onLayout 方法
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);
}
}
由于 layoutVertical 與 layoutHorizontal 方法實現(xiàn)類似农渊,所以只看 layoutVertical 方法的主要代碼
void layoutVertical(int left, int top, int right, int bottom) {
...
final int count = getVirtualChildCount();
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
}
...
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
可以看到,layoutVertical 會遍歷所有子元素并調(diào)用 setChildFrame 方法來給子元素指定位置或颊,其中 childTop 會逐漸增大腿时,這意味元素會從上到下排列。
看下 setChildFrame 方法
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
setChildFrame 中僅僅是調(diào)用子元素的 layout 方法饭宾。其中 width 和 height 是子元素的測量寬高
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
總結(jié)一下,父元素在 layout 方法中完成定位后格了,通過 onLayout 方法調(diào)用子元素的 layout 方法看铆,而子元素會通過 layout 方法來確定自己的位置,這樣一層層的傳遞就完成了 View 的 Layout 過程盛末。
在 View 工作原理(一)中有提到測量寬高大部分情況下等于最終寬高弹惦,之所以說大部分情況否淤,是因為測量寬高的賦值早于最終寬高,如果重寫 View 的 layout 方法
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r + 10, b + 10);
}
那么這種情況下棠隐,最終寬高不等于測量寬高石抡。另一種情況是,當(dāng) View 需要多次 measure 來測量寬高助泽,那么前幾次的測量值可能就不會與最終寬高相等啰扛。
Draw 過程
Draw 過程的作用是將 View 繪制到屏幕上,看 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);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we're done...
return;
}
...
}
于是得到主要步驟
- 繪制背景 drawBackground
- 繪制內(nèi)容 onDraw
- 繪制子元素 dispatchDraw
- 繪制裝飾 onDrawScrollBars
來看 drawBackground 方法
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
...
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
drawBackground 方法使用 scrollX 和 scrollY 記錄偏移值嗡贺,然后根據(jù)偏移值進(jìn)行繪制隐解。
由于 View 沒有實現(xiàn) onDraw 方法,所以先跳過诫睬。
在普通 View 中煞茫,因為它沒有子元素,所以 dispatchDraw 方法為空摄凡。于是看 ViewGroup 的 dispatchDraw 方法
protected void dispatchDraw(Canvas canvas) {
...
for (int i = 0; i < childrenCount; i++) {
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);
}
}
...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
可以看到续徽,dispatchDraw 方法會遍歷所有子元素的 draw 方法,這樣就將 Draw 過程傳遞了下去亲澡。
最后通過 onDrawScrollBars 方法繪制完滾動條(滾動條可能不顯示)钦扭。這樣 View 的 Draw 過程就完成了。