1. 簡(jiǎn)介
-
View的繪制過(guò)程分為三部分:measure、layout、draw。
measure用來(lái)測(cè)量View的寬和高。
layout用來(lái)計(jì)算View的位置。
draw用來(lái)繪制View。
經(jīng)過(guò)
measure之后就進(jìn)入了layout過(guò)程,measure過(guò)程可以查看這篇文章:自定義View原理篇(1)- measure過(guò)程。本章主要對(duì)
layout過(guò)程進(jìn)行詳細(xì)的分析。本文源碼基于android 27。
2. layout的始點(diǎn)
跟measure一樣,layout也是始于ViewRootImpl的performTraversals():
2.1 ViewRootImpl的performTraversals
private void performTraversals() {
//...
//獲得view寬高的測(cè)量規(guī)格,mWidth和mHeight表示窗口的寬高,lp.widthhe和lp.height表示DecorView根布局寬和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//執(zhí)行測(cè)量
//...
performLayout(lp, mWidth, mHeight);//執(zhí)行布局
//...
performDraw();//執(zhí)行繪制
//...
}
再來(lái)看看performLayout():
2.2 ViewRootImpl的performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
//...
//調(diào)用layout
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//...
}
這里的host就是DecorView,如果不知道DecorView,可以看看這篇文章:從setContentView揭開(kāi)DecorView。
layout()方法傳入的0,0,host.getMeasuredWidth,host.getMeasuredHeight就是一個(gè)View的上下左右四個(gè)位置,可以看到,DecorView都是從左上角位置(0,0)開(kāi)始進(jìn)行布局的,其寬高則為測(cè)量寬高。
下面重點(diǎn)來(lái)分析Layout過(guò)程
3.layout過(guò)程分析
layout用來(lái)計(jì)算View的位置,即確定View的Left、Top、Right 和 Bottom這四個(gè)頂點(diǎn)的位置。如下圖所示:

同樣,layout過(guò)程根據(jù)View的類型也可以分為兩種情況:
- 計(jì)算單一
View位置時(shí),只需計(jì)算其自身即可;- 計(jì)算
ViewGroup位置時(shí),需要計(jì)算ViewGroup自身的位置以及其包含的子View在ViewGroup中的位置。
我們對(duì)這兩種情況分別進(jìn)行分析。
3.1 單一View的layout過(guò)程
單一View的layout過(guò)程是從View的layout()方法開(kāi)始:
3.1.1 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;
//isLayoutModeOptical(mParent);//判斷該view布局模式是否有一些特殊的邊界
//有特殊邊界則調(diào)用setOpticalFrame(l, t, r, b)
//無(wú)特殊邊界則調(diào)用setFrame(l, t, r, b)
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 若View的大小或位置有變化
// 會(huì)重新確定該View所有的子View在父容器的位置,通過(guò)調(diào)用onLayout()來(lái)實(shí)現(xiàn)。
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
// ...
}
我們接下來(lái)分別看看setOpticalFrame(),setFrame(),onLayout()這三個(gè)方法。
3.1.2 View的setOpticalFrame
先來(lái)看看setOpticalFrame():
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
//調(diào)用setFrame()
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
setOpticalFrame()里面最終還是會(huì)調(diào)用到setFrame()
3.1.3 View的setFrame
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
//...
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
//...
//賦值,保存View的四個(gè)位置
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
//...
}
return changed;
}
可以看到,View的四個(gè)位置就在這里給確定下來(lái)了。
3.1.4 View的onLayout
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
onLayout()在View中就是個(gè)空實(shí)現(xiàn),由于單一的View沒(méi)有子View,因此不需要確定子View的布局,所以onLayout()也無(wú)需實(shí)現(xiàn)。
3.1.5 單一View的layout過(guò)程流程圖
所以,單一View的Layout還是很簡(jiǎn)單的,來(lái)張流程圖簡(jiǎn)單總結(jié)一下:

3.2 ViewGroup的layout過(guò)程
ViewGroup的layout過(guò)程除了需要計(jì)算ViewGroup自身的位置外,還需要計(jì)算其包含的子View在ViewGroup中的位置。
計(jì)算ViewGroup自身的位置實(shí)際上跟單一View的過(guò)程是一樣的,這里就不重述;唯一不同的就是單一View的onLayout()實(shí)現(xiàn)為空,ViewGroup需要具體實(shí)現(xiàn)onLayout()方法。
onLayout()方法在ViewGroup是一個(gè)抽象方法,需要其子類去重寫(xiě),因?yàn)榇_定子View的位置與具體的布局有關(guān),所以ViewGroup中沒(méi)有辦法統(tǒng)一實(shí)現(xiàn)。
我們?cè)谶@里看看LinearLayout的onLayout()實(shí)現(xiàn):
3.2.1 LinearLayout的onLayout
@Override
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會(huì)區(qū)分方向來(lái)進(jìn)行不同的layout方法,我們主要看下豎向的layoutVertical(),橫向的原理差不多這里就不看了。
3.2.2 LinearLayout的layoutVertical
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;//記錄子View的Top位置
int childLeft;//記錄子View的Left位置
// ...
// 子View的數(shù)量
final int count = getVirtualChildCount();
// ...
for (int i = 0; i < count; i++) {//遍歷子View
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//獲取子View的測(cè)量寬 / 高值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//...
//childTop加上子View的topMargin的值
childTop += lp.topMargin;
//調(diào)用setChildFrame(),這里確定子View的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
//childTop加上子View的高度、bottomMargin等值
//因此后面的子View就順延往下放,這符合垂直方向的LinearLayout的特性
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
//...
}
}
}
layoutVertical()通過(guò)遍歷子View,并調(diào)用setChildFrame()方法來(lái)確定子View的位置。
3.2.3 LinearLayout的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)用子View的layout()方法來(lái)來(lái)確定子View的位置。
3.2.4 ViewGroup的layout過(guò)程流程圖

4. 自定義View
4.1 自定義單一view
自定義單一view一般無(wú)需重寫(xiě)onLayout()方法。
4.2 自定義ViewGroup
由于ViewGroup沒(méi)實(shí)現(xiàn)onLayout(),所以自定義ViewGroup需要重寫(xiě)onLayout()方法。這里給個(gè)簡(jiǎn)單的模板:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//遍歷子View
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
//獲取當(dāng)前子View寬/高值
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
//計(jì)算當(dāng)前子View的四個(gè)位置值
int mLeft = l + 100 * i;//具體邏輯請(qǐng)自行計(jì)算
int mTop = t + 100 * i;//具體邏輯請(qǐng)自行計(jì)算
int mRight = mLeft + width;//具體邏輯請(qǐng)自行計(jì)算
int mBottom = mTop + height;//具體邏輯請(qǐng)自行計(jì)算
//根據(jù)上面的計(jì)算結(jié)果設(shè)置子View的4個(gè)頂點(diǎn)
child.layout(mLeft, mTop, mRight, mBottom);
}
}
5. 其他
5.1 getWidth()與getMeasuredWidth()區(qū)別,getHeight()與getMeasuredHeight()同理
getWidth():獲得View最終的寬;getMeasuredWidth():獲得View測(cè)量的寬;
一般情況下,這兩者獲得的值是一樣的,我們可以來(lái)看看他們的代碼實(shí)現(xiàn):
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getWidth() {
return mRight - mLeft;
}
結(jié)合源碼中的各種賦值過(guò)程,getWidth()的值就是測(cè)量出的寬度。
當(dāng)然,我們可以通過(guò)重寫(xiě)layout()來(lái)修改最終的寬度,但一般這沒(méi)有任何的實(shí)際意義,如:
@Override
public void layout(int l, int t, int r, int b) {
// 修改傳入的位置參數(shù),這樣一來(lái),getWidth()獲得的寬度就比測(cè)量出來(lái)的寬度大上100了
super.layout(l, t, r + 100, b + 100);
}