Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置。
先看View的layout方法,如下:
View#layout:
//注意,传入的参数分别是:
//当前View左上角相对于父容器左边缘的水平距离,
//当前View左上角相对于父容器上边缘的竖直距离,
//当前View右下角相对于父容器左边缘的水平距离,
//当前View右下角相对于父容器上边缘的竖直距离,
public void layout(int l, int t, int r, int b) {
//如果在前面的measeure方法中直接从缓存中获取而没有执行onMeasure方法
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;
//changed为ture表示当前View在父容器中位置发生变化
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 如果当前View的位置改变,或者mPrivateFlags 的PFLAG_LAYOUT_REQUIRED被强制置位,则调用onLayout重新布局子元素
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;
}
layout的大致流程:
- 通过setFrame方法来设定View的四个顶点的位置,即初始化mLeft、mRight、mTop和mBottom这四个值,View的四个顶点一旦确定,那么View在父容器中的位置也就确定了。
- 调用onLayout方法,在父容器中调用用于确定子元素的位置。和onMeasure方法类似,onLayout的具体实现同样和具体的布局有关,所以View和ViewGroup均没有真正实现onLayout方法。
分析LinearLayout:
//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);
}
}
//LinearLayout#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);
}
}
}
//LinearLayout#setChildFrame:
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
layoutVertical会遍历所有子元素并调用setChildFrame方法来为子元素指定对应的位置,其中childTop会逐渐增大,这意味着后面的子元素会被放置在靠下的位置,至于setChildFrame方法,它仅仅是调用子元素的layout方法而已。
这样父元素在layout方法中完成自己的定位以后,就通过onLayout方法去调用子元素的layout方法,子元素又会通过自己的layout方法来确定自己的位置,这样一层一层地传递下去就完成了整个View树的layout过程。