记录Linearlayout 三次测量

1、LinearLayout针对设置weight与不设置weight的情况分别处理
2、在 LinearLayout 中总共有 3 个 for 循环,分别处理不同的流程
第一个 for 循环,只会在不使用 weight 属性时进入,并有可能会测量每个 childView 的大小
第二个 for 循环,在使用 android:measureWithLargestChild 时才会进入,并且即使进入也不会调用 childView 的测量方法,只会更新 mTotalLength 变量
第三个 for 循环,只会在使用 weight 属性时进入,并测量每个 childView 的大小


	@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
    

源码如上所示 onMeasure(int widthMeasureSpec, int heightMeasureSpec) ,通过 mOrientation 分别处理垂直和水平两个方向的测量,其中的 mOrientation 变量则是我们在 xml 布局文件中通过 android:orientation=“vertical” 或者直接通过 setOrientation(@OrientationMode int orientation) 方法设置的 LinearLayout 文件方向变量

这里仅分析垂直方向的测量方法,也就是 measureVertical(int widthMeasureSpec, int heightMeasureSpec)(水平方向的测量方法 measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) 是类似的原理)

初始化变量

需要初始化一些类变量 & 声明一些重要的局部变量


void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
		//第一阶段,主要是一些变量的初始化
        mTotalLength = 0;// 所有 childView 的高度和 + 本身的 padding,注意:它和 LinearLayout 本身的高度是不同的
        int maxWidth = 0;// 所有 childView 中宽度的最大值
        int childState = 0;
        int alternativeMaxWidth = 0;// 所有 layout_weight <= 0 的 childView 中宽度的最大值
        int weightedMaxWidth = 0;// 所有 layout_weight >0 的 childView 中宽度的最大值
        boolean allFillParent = true;
        float totalWeight = 0;// 所有 childView 的 weight 之和

        final int count = getVirtualChildCount();

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        boolean matchWidth = false;
        boolean skippedMeasure = false;

        final int baselineChildIndex = mBaselineAlignedChildIndex;
        final boolean useLargestChild = mUseLargestChild;

        int largestChildHeight = Integer.MIN_VALUE;
        int consumedExcessSpace = 0;

        int nonSkippedChildCount = 0;
        
        ...

第一次测量

在测量第一阶段会计算那些没有设置 weight 的 childView 的高度、计算 mTotalLength,并且计算三个宽度相关的变量的值

接上面的代码
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
		//第二阶段,第一次测量,接上面代码
		//第一遍循环,看看每个childview的高度,并且记录最大宽度
		// See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {//一层for循环
            final View child = getVirtualChildAt(i);//获取到每一个childview
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }

            nonSkippedChildCount++;
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;//计算总权重

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;//使用了权重才会满足
            
            // 我们都知道,测量模式有三种:
            // * UNSPECIFIED:父控件对子控件无约束
            // * Exactly:父控件对子控件强约束,子控件永远在父控件边界内,越界则裁剪。如果要记忆的话,可以记忆为有对应的具体数值或者是Match_parent
            // * AT_Most:子控件为wrap_content的时候,测量值为AT
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {//确切高度,且height=0 权重>0
                // Optimization: don't bother measuring children who are only
                // laid out using excess space. These views will get measured
                // later if we have space to distribute.
                //先跳过测量模式为EXACTLY并且需要权重计算的childview         
                // 在后面第三个 for 循环重新计算此 childView 大小
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;//后面跳过Measure
            } else {//高度不是确定可能是AT_MOST/UNSPECIFIED
                if (useExcessSpace) {
                    // The heightMode is either UNSPECIFIED or AT_MOST, and
                    // this child is only laid out using excess space. Measure
                    // using WRAP_CONTENT so that we can find out the view's
                    // optimal height. We'll restore the original height of 0
                    // after measurement.
                    //把使用权重的childview的高度设置为wrap_content
                    lp.height = LayoutParams.WRAP_CONTENT;
                }

                // Determine how big this child would like to be. If this or
                // previous children have given a weight, then we allow it to
                // use all available space (and we will shrink things later
                // if needed).
                //这是非常重要的一个方法,将会决定每个 childView 的大小
                //如果此 childView 及在此 childView 之前的 childView 中使用了 weight 属性,
                // 我们允许此 childView 使用所有的空间(后续如果需要,再做调整)
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                //调用viewgroup中方法测量子view
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

               // 得到测量之后的 childView 的 childHeight
                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {
                    // Restore the original height and record how much space
                    // we've allocated to excess-only children so that we can
                    // match the behavior of EXACTLY measurement.
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

                 // 将此 childView 的 childHeight 加入到 mTotalLength 中
                // 并加上 childView 的 topMargin 和 bottomMargin 
                // getNextLocationOffset 方法返回 0,方便以后扩展使用
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);//记录最大子view高度
                }
            }

            // 下面两个 if 判断都和 `android:baselineAlignedChildIndex` 属性有关,这里不展开分析
            /**
             * If applicable, compute the additional offset to the child's baseline
             * we'll need later when asked {@link #getBaseline}.
             */
            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }

            // if we are trying to use a child index for our baseline, the above
            // book keeping only works if there are no children above it with
            // weight.  fail fast to aid the developer.
            if (i < baselineChildIndex && lp.weight > 0) {
                throw new RuntimeException("A child of LinearLayout with index "
                        + "less than mBaselineAlignedChildIndex has weight > 0, which "
                        + "won't work.  Either remove the weight, or don't set "
                        + "mBaselineAlignedChildIndex.");
            }

            boolean matchWidthLocally = false;//该子view是否需要测量宽度
            
            // 所有 widthMode 是 `MeasureSpec.EXACTLY`,不会进入此 if 判断 
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                // The width of the linear layout will scale, and at least one
                // child said it wanted to match our width. Set a flag
                // indicating that we need to remeasure at least that view when
                // we know our width.
                // 当父类(LinearLayout)不是match_parent或者精确值的时候,但子控件却是一个match_parent
               // 那么matchWidthLocally和matchWidth置为true
               // 意味着这个控件将会占据父类(水平方向)的所有空间
                matchWidth = true;
                matchWidthLocally = true;
            }

            // 计算三个和宽度相关的变量值
            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());//获取子viewmeasure后的state状态

            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
            if (lp.weight > 0) {
            //需要计算权重的
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 * alternative 可供选择的
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
            //如果不需要计算权重走这里
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }//for循环结束

       // 如果存在没有跳过的 childView 并且需要绘制 end divider 则需要加上 end 位置的 divider 的高度
        if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }

measureChildBeforeLayout()

在此方法中将会计算每个 childView 的大小,调用 ViewGroup 的 measureChildWithMargins() 方法计算每个 childView 的大小,在测量垂直方向的 childView 时,有一个非常重要的参数需要注意,即:heightUsed 是指在垂直方向,已经被 parentView 或者 parentView 的其他 childView 使用了的空间


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

第二次测量

如果进入这个 if 条件,会进行第二次的 for 循环遍历 childView,重新计算 mTotalLength


void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
		//接上面代码
		if (useLargestChild && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;

            //重新计算总高度:每个非gone的view的高度都按 上次循环记录的最大子view的高度计算,再加上margin
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }

                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }

                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                        child.getLayoutParams();
                // Account for negative margins
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }
        }

测量第三阶段

针对设置了 android:layout_weight 属性的布局,重新计算 mTotalLength


void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
		//接上面代码
        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        
        // 通过 getSuggestedMinimumHeight() 得到建议最小高度,并和计算得到的
        // mTotalLength 比较取最大值
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        // Reconcile our calculated size with the heightMeasureSpec
       // 通过 heightMeasureSpec,调整 heightSize 的大小
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        // Either expand children with weight to take up available space or
        // shrink them if they extend beyond our current bounds. If we skipped
        // measurement on any children, we need to measure them now.
        
        // 重新计算有 weight 属性的 childView 大小,
        // 如果还有可用的空间,则扩展 childView,计算其大小
        // 如果 childView 超出了 LinearLayout 的边界,则收缩 childView
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        if (skippedMeasure
                || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {    
            // 根据 mWeightSum 计算得到 remainingWeightSum,mWeightSum 是通过 
            // `android:weightSum` 属性设置的,totalWeight 是通过第一次 for 循环计算得到的
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
            // 将 mTotalLength 复位为 0
            mTotalLength = 0;
            // 权重childview的测量,开始真正的第二次 for 循环遍历每一个 childView,重新测量每一个 childView
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null || child.getVisibility() == View.GONE) {
                    continue;
                }

                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final float childWeight = lp.weight              
               // 如果该 childView 设置了 `weight` 值,则进入 if 语句块
                if (childWeight > 0) {
                    // 这是设置了 weight 的情况下,最重要的一行代码
                    // remainingExcess 剩余高度 * ( childView 的 weight / remainingWeightSum)
                    // share 便是此 childView 通过这个公式计算得到的高度,                               
                    // 并重新计算剩余高度 remainingExcess 和剩余权重总和 remainingWeightSum
                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                    remainingExcess -= share;
                    remainingWeightSum -= childWeight;

                   // 通过下面的 if 条件重新计算,childHeight 是最终 childView 的真正高度
                    final int childHeight;
                    if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                        childHeight = largestChildHeight;
                    } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                            || heightMode == MeasureSpec.EXACTLY)) {
                        // This child needs to be laid out from scratch using
                        // only its share of excess space.
                        childHeight = share;
                    } else {
                        // This child had some intrinsic height to which we
                        // need to add its share of excess space.
                        childHeight = child.getMeasuredHeight() + share;
                    }

                    // 计算 childHeightMeasureSpec & childWidthMeasureSpec,并调用 child.measure() 方法
                    final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.max(0, childHeight), MeasureSpec.EXACTLY);
                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                            lp.width);
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Child may now not fit in vertical dimension.
                    childState = combineMeasuredStates(childState, child.getMeasuredState()
                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                }

                final int margin =  lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);

                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                        lp.width == LayoutParams.MATCH_PARENT;

                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);

                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

                // 考虑 childView.topMargin & childView.bottomMargin,重新计算 mTotalLength
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }

            // Add in our padding
           // 完成 for 循环之后,加入 LinearLayout 本身的 mPaddingTop & mPaddingBottom
            mTotalLength += mPaddingTop + mPaddingBottom;
            // TODO: Should we recompute the heightSpec based on the new total length?
        } else {
            // 重新计算 alternativeMaxWidth
            alternativeMaxWidth = Math.max(alternativeMaxWidth, weightedMaxWidth);


            // We have no limit, so make all weighted views as tall as the largest child.
            // Children will have already been measured once.
            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
                for (int i = 0; i < count; i++) {
                    final View child = getVirtualChildAt(i);
                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }

                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();

                    float childExtra = lp.weight;
                    if (childExtra > 0) {
                        child.measure(
                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                        MeasureSpec.EXACTLY),
                                MeasureSpec.makeMeasureSpec(largestChildHeight,
                                        MeasureSpec.EXACTLY));
                    }
                }
            }
        }

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }

        // 调整 width 大小
        maxWidth += mPaddingLeft + mPaddingRight;

        // Check against our minimum width
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // 调用 setMeasuredDimension() 设置 LinearLayout 的大小
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        //最后,设置LinearLayout的size大小和状态,如果LinearLayout有设置width为match_parent的话,将会调用forceUniformWidth再测量一次所有的subchild,这里主要是测量subchild的width大小
        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }

假如一共有3个subchild且都有设置weight ,分别为3、2、1,我们假设剩余的space为120,则第一个view的大小为120 * 3/(3+2+1)=60,第二个view的大小为(120-60)*2/(2+1)=40,第3个view的大小为(60-40)*1/1 = 20

resolveSizeAndState

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值