二十一、 View 的工作原理(5)--- View 的 layout 过程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yz_cfm/article/details/90813542

    前面学习了 View 三大流程中的 measure 过程,measure 过程确定了 View 的测量宽/高。这篇学习三大流程中的 layout 过程,它确定了 View 的最终宽/高和四个顶点的位置。我们知道,Android 中所有的控件组成可以看成一个 View 树状结构,总体分为两类:不包含子元素的普通 View 和包含子元素的 ViewGroup。当 ViewGroup 位置确定后,它会调用 onLayout() 方法,并在该方法中遍历其子元素并调用其 layout() 方法,接着继续在 layout() 方法中调用 onLaout() 方法,如此循环,知道将所有的 View 位置都确定完。所以综上:layout() 方法就是确定 View 本身的位置,而 onLayout() 方法则会确定所有子元素的位置。

    我们先来分析 View 中的 layout() 方法:

public void layout(int l, int t, int r, int b) {
    ...
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    // setFrame() 用来设定 View 的四个顶点的位置。即初始化 mLeft、mRight、mTop 和 mBottom。
    // 当 View 的四个顶点确定好了也就相当于 View 在父容器中的位置确定好了
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        // 接着调用 onLayout() 方法来确定该 View 中子元素的位置,循环往复,直到该 View 本身及其所有的子元素的
        // 位置都确定完毕。
        onLayout(changed, l, t, r, b);
        ...
    }
    ...
}

    与 onMeasure() 类似,由于不同的 ViewGroup 子类常作为父类容器使用,比如 LinearLayout、RelativeLayout 等等,它们有不同的布局特性,这导致它们的 layout 过程细节各不相同,所以 ViewGroup 无法做统一实现。下面我们分析一下 Android 中提供的继承了 ViewGroup 的 LinearLayout 中 onLayout() 的具体实现,以此来分析 ViewGroup 的 layout 过程。

@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);
    }
}

    接着,我们继续以 layoutVertical() 来分析:

void layoutVertical(int left, int top, int right, int bottom) {
    ...
    // 遍历子元素
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            // 获取子元素测量(measure 过程)后的宽/高
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();

            // 获取子元素的 LayoutParams
            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();
            ...
            childTop += lp.topMargin;

            // 为子元素指定对应的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);

            // 子元素的 Top 变大,这是因为父容器的布局方向为竖直的
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
            i += getChildrenSkipCount(child, i);
        }
    }
}

    可以看到,layoutVertical() 方法的关键就是 setChildFrame() 方法,接着看看它做了些什么:

private void setChildFrame(View child, int left, int top, int width, int height) {
    child.layout(left, top, left + width, top + height);
}

    可以看到,setChildFrame() 方法仅仅是调用子元素的 layout() 方法,回到前面分析 layout() 方法我们知道,在 layout() 方法中又继续调用 onLayout() 方法,然后继续调用 layout() 方法,如此循环往复,直到该子元素以及该子元素内部的子元素的位置全部确定结束。这样一层一层地传递下去就完成了整个 View 树的 layout 过程。

    分析完 layout 过程,我们再回到 layoutVertical() 方法中,可以看到 setChildFrame() 中的 width 和 height 就是子元素的测量宽/高。最开始分析过,view 四个顶点位置的确定方法为 setFrame(),如下:

protected boolean setFrame(int left, int top, int right, int bottom) {
    ...
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
    ...
}

public final int getWidth() {
    return mRight - mLeft;
}

public final int getHeight() {
    return mBottom - mTop;
}

    所以,正常来说,我们在 layout() 过程完成后,通过 getWidth() 和 getHeight() 方法获取到的宽高和 measure 过程完成后通过 getMeasuredWidth() 和 getMeasuredHeight() 方法获取到的宽高一致才对啊?那为什么前面我们说,在极端情况下,View 的测量宽高和最终宽高有可能不一样呢?这里的极端情况其实就是我们人为的干预 layout 过程,比如重写 layout 方法:

@Override
public void layout(int l, int t, int r, int b) {
    super.layout(l, t, r + 100, b + 100);
}

    虽然这样做没有什么实际意义,但确实在结果上 layout 过程后的最终宽高比 measure 过程后的测量宽高都大 100px。综上:在 View 的默认实现中,View 的测量宽高(形成于 measure 过程)和最终宽高(形成于 layout 过程)大小一定是相等的,只是得到的时机不同而已。还有一种情况,由于 View 在某些情况下需要多次 measure 过程才能确定自己的测量宽高,但最终来说,View 的测量宽高和最终宽高大小还是一样的。

猜你喜欢

转载自blog.csdn.net/yz_cfm/article/details/90813542