layout(布局)的作用是ViewGroup用来确定子元素的位置,在这个过程中会用到两个核心方法: layout() 和 onLayout() 。layout()方法用来确定View本身的位置,onLayout()方法则用来确定所有子元素的位置。View和ViewGroup中都有layout()和onLayout()两个方法,但两个类中都没有实现onLayout(),其原因和ViewGroup中没有onMeasure()方法是相同的:因为不同ViewGroup的子类对布局的要求不一样。

  当我们自定义了一个ViewGroup的时候,会先确定这个ViewGroup的位置,然后,通过重写 onLayout() 方法,遍历所有的子元素并调用其 layout() 方法,在layout()方法中onLayout()方法又会被调用。ViewGroup就是通过这个过程,递归地对所有子View进行了布局。来看一下View类中的layout()方法的源码:

/**
* 本方法用来给一个View和它的所有子View设置尺寸和位置;
* 这是Android布局机制的第二个阶段(第一个阶段是测量);
* 在这个阶段中,每个父容器都调用layout()方法来定位它的子View;
* 子类不能重写这个方法,而应该重写onLayout()方法;
* 在onLayout()方法中调用layout()方法来设置每个子View的位置。
*
* @param l 相对于父容器左边的距离
* @param t 相对于父容器上边的距离
* @param r 相对于父容器右边的距离
* @param b 相对于父容器下边的距离
*/
@SuppressWarnings({"unchecked"})
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); if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
} mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<View.OnLayoutChangeListener> listenersCopy =
(ArrayList<View.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() 方法来设置View的四个位置元素的位置,即初始化mLeft、mTop、mRight和mBottom这四个值。View的四个顶点一旦确定,那么View在父容器中的位置也就确定了;接着会调用 onLayout() 方法,这个方法的用途是父容器确定子元素的位置。

  上面提到,ViewGroup中没有实现onLayout()方法,原因是不同ViewGroup子类的布局方式不同,因此onLayout()的具体实现需要根据这个ViewGroup子类的布局方式来确定。比如,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);
}
}

  在自定义布局的时候,我们的任务就是:遍历所有的子元素,确定它们的大小和位置(大小主要是通过 getMeasuredWidth() 和 getMeasuredHeight() 两个方法,取出在 onMeasure() 方法中测量得到的宽/高;位置需要自行设置),然后调用 view.layout() 方法或直接调用ViewGroup中的方法 setChildFrame() 方法(setChildFrame()方法内部调用的就是view.layout()方法),将子元素布局到这个ViewGroup中。

  最后还需要说明一点,“测量宽/高” 和 “最终宽/高” 是两个不同的概念。测量宽/高是在onMeasure()方法中测量得到的宽度或高度,而最终宽/高是在onLayout()方法中最终放置的子元素的宽度或高度。在View的默认实现中,View的测量宽/高和最终宽/高是相等的,但是测量宽/高的赋值时机较早。

05-17 02:07