View体系(九)从LinearLayout分析ViewGroup的测量流程

158 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

之前的文章《View体系(八)深入剖析View的onMeasure方法》我们深入分析了ViewonMeasure方法,我们今天就来看一下ViewGroup的测量流程。

(注:文中源码基于 Android 12

View做测量时,会调用ViewonMeasure方法,但是我们翻看ViewGroup的源码,并没有发现onMeasure方法,难道ViewGroup不用测量?显然不是,我们换一个类来看,LinearLayout继承自ViewGroup,我们从LinearLayout的源码中找到了熟悉的onMeasure方法,我们看一下源码:

public class LinearLayout extends ViewGroup {
    ...
        @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {//1
            measureVertical(widthMeasureSpec, heightMeasureSpec);//2
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
    ... 

注释1处根据mOrientation判断是横向排列还是纵向排列来进入不同的测量流程,我们以纵向排列为例,看LinearLayout是如何测量其高度的,进入注释2处的measureVertical方法:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    mTotalLength = 0;
    final int count = getVirtualChildCount();//1
    ...
    for (int i = 0; i < count; ++i) {
        final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
        if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {//2
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);//3

                final int childHeight = child.getMeasuredHeight();//4
                if (useExcessSpace) {
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));//5

由于measureVertical方法很长,这里只贴出部分关键代码。注释1处获取子View的数量,注释2处遍历子View并根据测量模式进入不同的if-else分支,这里以wrap_content为例(即AT_MOST模式),则进入else分支,注释3处对子View进行测量,注释4处获取测量到的子View的高度,注释5处对子View的测量结果进行累加作为最终LinearLayout的测量高度。我们进入注释3的measureChildBeforeLayout方法:

    void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }

再进入measureChildWithMargins方法:

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);//1
    }

注释1处调用了child.measure方法:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);

measure又调用了ViewonMeasure方法,对于ViewonMeasure方法,之前的文章《View体系(八)深入剖析View的onMeasure方法》已经讲过,这里不再赘述。

至此LinearLayout的测量已经很清楚了,即对于纵向的LinearLayout并指定高度为wrap_content来说,是通过遍历测量子View的高度并累加作为自己的高度。至于为什么ViewGroup没有onMeasure方法,是因为它无法统一究竟要以什么方式来测量,所以它要让它的子类来各自实现测量方法,最终它的子类又重写onMeasure完成对自己的测量。

学习更多知识,请关注我的个人博客:droidYu