View工作原理 -- 工作过程 -- layout

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 listenersCopy =
                    (ArrayList)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的大致流程:

  1. 通过setFrame方法来设定View的四个顶点的位置,即初始化mLeft、mRight、mTop和mBottom这四个值,View的四个顶点一旦确定,那么View在父容器中的位置也就确定了。
  2. 调用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过程。

你可能感兴趣的:(View工作原理 -- 工作过程 -- layout)