和視圖測量類似确丢,布局的入口也在ViewRootImpl類霎箍,在performTraversals方法,測量完成后魏保,進行視圖布局熬尺。
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
...
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
performLayout方法。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;//設置正在布局標志
final View host = mView;
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;//布局完畢
int numViewsRequestingLayout = mLayoutRequesters.size();
....
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
從頂層視圖的layout方法開始谓罗,它在ViewGroup中定義粱哼,和measure方法一樣,final方法檩咱,不允許容器視圖子類重寫揭措,是一套模板。在layout方法中刻蚯,調用View的layout方法绊含。
public final void layout(int l, int t, int r, int b) {
super.layout(l, t, r, b);//調用View的layout
...
}
每個類型的視圖容器布局方式不同,比如炊汹,LinearLayout是順序布局躬充,分垂直和水平,RelativeLayout是相對布局讨便。在View的layout方法中充甚,onLayout方法實現(xiàn)不同的布局類型。
public void layout(int l, int t, int r, int b) {
...
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);//觸發(fā)onLayout器钟,容器類重寫此方法
ListenerInfo li = mListenerInfo;//監(jiān)聽改變
...
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}
ViewGroup的onLayout是抽象方法津坑,我們在重寫一種ViewGroup時,必須實現(xiàn)這個方法傲霸。葉子視圖的onLayout方法是空方法疆瑰。
View的layout方法還會通過setFrame方法設置內(nèi)部四個變量眉反,mLeft,mRight穆役,mTop寸五,mBottom,他們代表該視圖相對父視圖的位置耿币。
頂層視圖是FrameLayout布局梳杏,下面我們就看一下幀布局的onLayout方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
調用layoutChildren方法淹接,布局它的子視圖十性,將已經(jīng)確定好寬高大小的子視圖放到合適的位置。
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
final int count = getChildCount();
//childView在容器放置的位置
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {//非GONE
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//childView測量的寬高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity,
layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//兩個switch目的就是計算childLeft和childTop
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
遍歷子視圖layout方法塑悼。若子視圖是葉子節(jié)點劲适,調用View的layout,View的onLayout是空方法厢蒜。若子視圖是容器視圖霞势,類似FrameLayout,繼續(xù)向下遍歷斑鸦,直到遇到葉子節(jié)點執(zhí)行onLayout愕贡。樹形結構布局順序與測量順序一致。
關鍵點是計算子視圖位于容器的左和上位置巷屿,即childLeft固以,childTop,然后嘱巾,再加上測量子視圖得到的寬高嘴纺,就可以得出下和右位置。
從子視圖的LayoutParams參數(shù)中獲取gravity位置浓冒,該值決定它在容器的位置,包括Gravity.LEFT 尖坤、Gravity.RIGHT稳懒、Gravity.BOTTOM、Gravity.TOP慢味,參與決策的還包括子視圖自身Margin场梆。
子視圖的layout方法將這個四個參數(shù)傳入,目的是告訴子視圖纯路,它在容器中的相對布局坐標Rect區(qū)域或油。
四個參數(shù)值是相對容器坐標系(0,0,容器寬,容器高)的Rect驰唬。layoutChildren的四個參數(shù)代表著該視圖在他的父容器中的坐標系中相對的位置顶岸,因為DecorView已是頂層視圖腔彰,該值是窗體Rect。
總結
1辖佣,容器視圖布局的入口是ViewGroup的layout方法霹抛,final類型,ViewGroup的子類無法重寫卷谈,會調用父類View的layout方法杯拐。
2,View的layout方法世蔗,如果是葉子節(jié)點視圖調用端逼,會將相對父視圖的位置存儲在四個變量中,然后執(zhí)行的onLayout方法是空方法污淋。如果是容器視圖調用顶滩,正如1所說,是ViewGroip子類調用芙沥,也會存儲相對變量诲祸,然后執(zhí)行的onLayout方法ViewGroup子類會實現(xiàn)。
3而昨,自定義一個ViewGroup視圖時救氯,必須實現(xiàn)onLayout方法,通過View類的layout方法進入onLayout方法中執(zhí)行歌憨,計算子視圖在父視圖的上下/左右區(qū)域着憨,在該方法中遍歷子視圖的layout方法,區(qū)域通知子視圖务嫡。
4甲抖,不管是View還是ViewGroup,只有調用了layout心铃,設置了相對父視圖的位置准谚,才可以獲取getWidth和getHeight。
任重而道遠