被人忽略的LinearLayout

1,425 阅读3分钟

一.开篇

LinearLayout的使用比较简单,本身也没有关于事件分发的重写,layout_weight的设计是一个亮眼的地方,了解内部是如何工作的对于自己开发自定义View也会有很多帮助。

当height设置match_parent时,当height设置0时恰好相反,第一张图是match,第二张是0dp,当然wrap也是不一样的。

二.height设置0时 源码解析

LinearLayout有水平和垂直两种情况,这边分析垂直的情况,View的绘制流程这边也只关注onMeasure,其他情况都比较简单,按照 height==0 分析。 看下代码

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

mOrientation就是XML中设置的,先看下一些局部变量

 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0; // 子View的高度之和 不等于LinearLayout的高度
        int maxWidth = 0;  //  
        int childState = 0; // 
        int alternativeMaxWidth = 0; // 
        int weightedMaxWidth = 0; // 
        boolean allFillParent = true;  //
        float totalWeight = 0;  // 

        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;
        ....
        }

再看下如何对这些变量赋值的,分为三次循环

2.1 第一次循环

        // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                // child为空 算作高度为0 
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               // child 不显示的时候跳过
               i += getChildrenSkipCount(child, i);
               continue;
            }

            nonSkippedChildCount++;
            if (hasDividerBeforeChildAt(i)) {
                // 计算分割线的高度加入
                mTotalLength += mDividerHeight;
            }

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

            totalWeight += lp.weight;
            // 高度为0 weight大于0的时候为true 
            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                //  useExcessSpace为ture 时先不计算
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                if (useExcessSpace) {
                    lp.height = LayoutParams.WRAP_CONTENT;
                }
                  // 如果这个View之前的View有应用weight的,usedHeight就为0,(这种情况需要二次计算)
                否则就是整个子组件的总高度
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {
                    // height == 0 的时候会重制height
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

上面这个过程可以看出当有weight的时候 会出现计算子View height过高的问题,当然这只是第一次循环。

2.2 第二次循环

// 这个循环的条件是 useLargestChild为ture 这个需要xml设置
if (useLargestChild &&
                (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;

            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();
                if (isExactly) {
                    mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
                            getNextLocationOffset(child);
                } else {
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
                            lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
                }
            }
        }

2.3 第三次循环


            for (int i = 0; i < count; ++i) {
                ...
                 // mTotalLength == 0
                 int heightSize = mTotalLength;
                 // getSuggestedMinimumHeight 默认为0 需要自己设置 一般就是heightSize
                 heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
                 // resolveSizeAndState 基本就是对父组件height 和 0进行位或操作 ,heightSize基本也不会发生改变 
                 int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
                  // MEASURED_SIZE_MASK==0x00ffffff ,heightSize 和这个值与操作 ,因为height值不够大 计算结果肯定还是这个值
                 heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
                 int remainingExcess = heightSize - mTotalLength
                   + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
                  if (childWeight > 0) {
                    // remainingExcess是剩下的空间 用于分配weight的控件
                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                    remainingExcess -= share;
                    remainingWeightSum -= childWeight;

                    final int childHeight;
                    // 从这个if的走向可以看出 odp height的时候 按照weight分配 大的分配到大的height 
                    if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                        childHeight = largestChildHeight;
                    } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                            || heightMode == MeasureSpec.EXACTLY)) {
                        childHeight = share;
                    } else {
                        // share可能为负值 
                        childHeight = child.getMeasuredHeight() + share;
                    }

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

三.当对于height == match_parent时 源码解析

这边只列出不同之处

3.1 第一次循环

totalHeight = 2*父组件的高度

3.2 第二次循环

一样

3.3 第三次循环

remainingExcess = -父组件的高度

四.当对于height == wrap_content时 源码解析

这边只列出不同之处

4.1 第一次循环

totalHeight 约等于 2*文本的高度

4.2 第二次循环

一样

4.3 第三次循环

remainingExcess = 父组件的高度-2*文本的高度

总结:

1.单个weight高度公式 :height = (父组件高度-子组件原高度之和)*weight/weightSum +单个子组件原高度

2.有weight的情况会执行两遍子view的measure