LinearLayout源码分析

LinearLayout 作为常用的布局之一,分为水平方向和垂直方向,另外我们经常用到下面的用法 使多个子View均分宽度或高度

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/test_tv"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Hello World!" />

    <TextView
        android:id="@+id/test_tv2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Hello World2!" />
</LinearLayout>

下面我们来看一下Mearsure 过程:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
       //在垂直方向测量
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        // 在水平方向测量
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

下面我们来分析一种measureHorizontal就可以 ,源代码如下:

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

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

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

        totalWeight += lp.weight;

        // 判断子View 是否设置了layout_weight >0,layout_width = 0
        final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;
        // 判断LinearLayout 的宽度是否是固定大小或者Match_Parent,同时useExcessSpace
        if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
            // 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.
            if (isExactly) {
                // 此时View没有测量,总宽度为所有view的左右margin相加
                mTotalLength += lp.leftMargin + lp.rightMargin;
            } else {
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength +
                        lp.leftMargin + lp.rightMargin);
            }

            // Baseline alignment requires to measure widgets to obtain the
            // baseline offset (in particular for TextViews). The following
            // defeats the optimization mentioned above. Allow the child to
            // use as much space as it wants because we can shrink things
            // later (and re-measure).
            if (baselineAligned) {
                final int freeWidthSpec = MeasureSpec.makeSafeMeasureSpec(
                        MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.UNSPECIFIED);
                final int freeHeightSpec = MeasureSpec.makeSafeMeasureSpec(
                        MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED);
                child.measure(freeWidthSpec, freeHeightSpec);
            } else {
                skippedMeasure = true;
            }
        } else {
            if (useExcessSpace) {
                // The widthMode 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 width. We'll restore the original width of 0
                // after measurement.
                //view设置了layout_width = 0,layout_weight > 0,但是LinearLayout宽度不固定或者不是Match_Parent
               // 则设置view的宽度为wrap_content
                lp.width = 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).
            final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
            //测量view
            measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
                    heightMeasureSpec, 0);

            final int childWidth = child.getMeasuredWidth();
            if (useExcessSpace) {
                // Restore the original width and record how much space
                // we've allocated to excess-only children so that we can
                // match the behavior of EXACTLY measurement.
                lp.width = 0;
                usedExcessSpace += childWidth;
            }

            if (isExactly) {
                mTotalLength += childWidth + lp.leftMargin + lp.rightMargin
                        + getNextLocationOffset(child);
            } else {
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin
                        + lp.rightMargin + getNextLocationOffset(child));
            }

            if (useLargestChild) {
                largestChildWidth = Math.max(childWidth, largestChildWidth);
            }
        }

        boolean matchHeightLocally = false;
        if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT) {
            // The height of the linear layout will scale, and at least one
            // child said it wanted to match our height. Set a flag indicating that
            // we need to remeasure at least that view when we know our height.
            matchHeight = true;
            matchHeightLocally = true;
        }

        final int margin = lp.topMargin + lp.bottomMargin;
        final int childHeight = child.getMeasuredHeight() + margin;
        childState = combineMeasuredStates(childState, child.getMeasuredState());

        if (baselineAligned) {
            final int childBaseline = child.getBaseline();
            if (childBaseline != -1) {
                // Translates the child's vertical gravity into an index
                // in the range 0..VERTICAL_GRAVITY_COUNT
                final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity)
                        & Gravity.VERTICAL_GRAVITY_MASK;
                final int index = ((gravity >> Gravity.AXIS_Y_SHIFT)
                        & ~Gravity.AXIS_SPECIFIED) >> 1;

                maxAscent[index] = Math.max(maxAscent[index], childBaseline);
                maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline);
            }
        }

        maxHeight = Math.max(maxHeight, childHeight);

        allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
        if (lp.weight > 0) {
            /*
             * Heights of weighted Views are bogus if we end up
             * remeasuring, so keep them separate.
             */
            weightedMaxHeight = Math.max(weightedMaxHeight,
                    matchHeightLocally ? margin : childHeight);
        } else {
            alternativeMaxHeight = Math.max(alternativeMaxHeight,
                    matchHeightLocally ? margin : childHeight);
        }

        i += getChildrenSkipCount(child, i);
    }

    if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
        mTotalLength += mDividerWidth;
    }

    // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
    // the most common case
    if (maxAscent[INDEX_TOP] != -1 ||
            maxAscent[INDEX_CENTER_VERTICAL] != -1 ||
            maxAscent[INDEX_BOTTOM] != -1 ||
            maxAscent[INDEX_FILL] != -1) {
        final int ascent = Math.max(maxAscent[INDEX_FILL],
                Math.max(maxAscent[INDEX_CENTER_VERTICAL],
                Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
        final int descent = Math.max(maxDescent[INDEX_FILL],
                Math.max(maxDescent[INDEX_CENTER_VERTICAL],
                Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
        maxHeight = Math.max(maxHeight, ascent + descent);
    }

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

    // Add in our padding
    mTotalLength += mPaddingLeft + mPaddingRight;

    int widthSize = mTotalLength;

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

    // Reconcile our calculated size with the widthMeasureSpec
    int widthSizeAndState = resolveSizeAndState(widthSize, widthMeasureSpec, 0);
    widthSize = widthSizeAndState & 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.
    int remainingExcess = widthSize - mTotalLength
            + (mAllowInconsistentMeasurement ? 0 : usedExcessSpace);
    // 如果totalWeight > 0 ,重新测量
    if (skippedMeasure
            || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
        float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

        maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
        maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
        maxHeight = -1;

        mTotalLength = 0;

        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;
            if (childWeight > 0) {
                // 根据weight来计算每个view的宽度,
                final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                //剩余空间等于当前空间减去当前view的宽度
                remainingExcess -= share;
                // 剩余weight等于当前weightSum-当前view 的weight
                remainingWeightSum -= childWeight;

                final int childWidth;
                if (mUseLargestChild && widthMode != MeasureSpec.EXACTLY) {
                    childWidth = largestChildWidth;
                } else if (lp.width == 0 && (!mAllowInconsistentMeasurement
                        || widthMode == MeasureSpec.EXACTLY)) {
                    // This child needs to be laid out from scratch using
                    // only its share of excess space.
                    childWidth = share;
                } else {
                    // This child had some intrinsic width to which we
                    // need to add its share of excess space.
                    childWidth = child.getMeasuredWidth() + share;
                }

                final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        Math.max(0, childWidth), MeasureSpec.EXACTLY);
                final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                        mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin,
                        lp.height);
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

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

            if (isExactly) {
                mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin +
                        getNextLocationOffset(child);
            } else {
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() +
                        lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
            }

            boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY &&
                    lp.height == LayoutParams.MATCH_PARENT;

            final int margin = lp.topMargin + lp .bottomMargin;
            int childHeight = child.getMeasuredHeight() + margin;
            maxHeight = Math.max(maxHeight, childHeight);
            alternativeMaxHeight = Math.max(alternativeMaxHeight,
                    matchHeightLocally ? margin : childHeight);

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

            if (baselineAligned) {
                final int childBaseline = child.getBaseline();
                if (childBaseline != -1) {
                    // Translates the child's vertical gravity into an index in the range 0..2
                    final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity)
                            & Gravity.VERTICAL_GRAVITY_MASK;
                    final int index = ((gravity >> Gravity.AXIS_Y_SHIFT)
                            & ~Gravity.AXIS_SPECIFIED) >> 1;

                    maxAscent[index] = Math.max(maxAscent[index], childBaseline);
                    maxDescent[index] = Math.max(maxDescent[index],
                            childHeight - childBaseline);
                }
            }
        }

        // Add in our padding
        mTotalLength += mPaddingLeft + mPaddingRight;
        // TODO: Should we update widthSize with the new total length?

        // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
        // the most common case
        if (maxAscent[INDEX_TOP] != -1 ||
                maxAscent[INDEX_CENTER_VERTICAL] != -1 ||
                maxAscent[INDEX_BOTTOM] != -1 ||
                maxAscent[INDEX_FILL] != -1) {
            final int ascent = Math.max(maxAscent[INDEX_FILL],
                    Math.max(maxAscent[INDEX_CENTER_VERTICAL],
                    Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
            final int descent = Math.max(maxDescent[INDEX_FILL],
                    Math.max(maxDescent[INDEX_CENTER_VERTICAL],
                    Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
            maxHeight = Math.max(maxHeight, ascent + descent);
        }
    } else {
        alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight);

        // We have no limit, so make all weighted views as wide as the largest child.
        // Children will have already been measured once.
        if (useLargestChild && widthMode != 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(largestChildWidth, MeasureSpec.EXACTLY),
                            MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
                                    MeasureSpec.EXACTLY));
                }
            }
        }
    }

    if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
        maxHeight = alternativeMaxHeight;
    }

    maxHeight += mPaddingTop + mPaddingBottom;

    // Check against our minimum height
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());

    setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    (childState<<MEASURED_HEIGHT_STATE_SHIFT)));

    if (matchHeight) {
        forceUniformHeight(count, widthMeasureSpec);
    }
}

总结:measureHorizontal过程

1 遍历所有的view,判断当前view的参数,LinearLayout 设置了固定宽度或者Match_Parent && layout_width = 0 && layout_weight > 0 ?

如果上面条件成立,则所有的view暂时不测量,每行的已占用宽度为所有View的左右margin之和, 后面会根据layout_weight再次测量

2 如果上述两条不成立,如果宽度不固定或者Wrap_content && layout_width = 0 && layout_weight > 0,

则所有的view宽度为wrap_content

3  真正为 totalWeight > 0.0f这种情况下测量所有的view

// 通常skippedMeasure为true 
if (skippedMeasure
        || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
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;
     // layout_weight > 0 单独测量
    if (childWeight > 0) {
        final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
        remainingExcess -= share;
        remainingWeightSum -= childWeight;

        final int childWidth;
        if (mUseLargestChild && widthMode != MeasureSpec.EXACTLY) {
            childWidth = largestChildWidth;
        } else if (lp.width == 0 && (!mAllowInconsistentMeasurement
                || widthMode == MeasureSpec.EXACTLY)) {
            // This child needs to be laid out from scratch using
            // only its share of excess space.
            childWidth = share;
        } else {
            // This child had some intrinsic width to which we
            // need to add its share of excess space.
            childWidth = child.getMeasuredWidth() + share;
        }

        final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                Math.max(0, childWidth), MeasureSpec.EXACTLY);
        final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin,
                lp.height);
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

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

    // 所有的view测量完毕,计算总的宽度
    if (isExactly) {
        mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin +
                getNextLocationOffset(child);
    } else {
        final int totalLength = mTotalLength;
        mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() +
                lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
    }
}

总结起来:LinearLayout开始会有一次测量,但是如果LinearLayout的宽度固定或者宽度为Match_Parent,其view的layout_width == 0 && layout_weight > 0,此时不会真正的去测量view的宽高,  后面会通过另一次测量去计算,如果上面条件不满足,则在当前循环中,就会测量view的宽,高

 

 

LinearLayout layout 过程,我们也只分析layoutHorizontal过程

void layoutHorizontal(int left, int top, int right, int bottom) {
    final boolean isLayoutRtl = isLayoutRtl();
    final int paddingTop = mPaddingTop;

    int childTop;
    int childLeft;

    // Where bottom of child should go
    final int height = bottom - top;
    int childBottom = height - mPaddingBottom;

    // Space available for child
    int childSpace = height - paddingTop - mPaddingBottom;

    final int count = getVirtualChildCount();

    final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;

    final boolean baselineAligned = mBaselineAligned;

    final int[] maxAscent = mMaxAscent;
    final int[] maxDescent = mMaxDescent;

    final int layoutDirection = getLayoutDirection();
   // LinearLayout中布局开始时水平方向的left,right
    switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) {
        case Gravity.RIGHT:
            // mTotalLength contains the padding already
            childLeft = mPaddingLeft + right - left - mTotalLength;
            break;

        case Gravity.CENTER_HORIZONTAL:
            // mTotalLength contains the padding already
            childLeft = mPaddingLeft + (right - left - mTotalLength) / 2;
            break;

        case Gravity.LEFT:
        default:
            childLeft = mPaddingLeft;
            break;
    }

    int start = 0;
    int dir = 1;
    //In case of RTL, start drawing from the last child.
    if (isLayoutRtl) {
        start = count - 1;
        dir = -1;
    }

    for (int i = 0; i < count; i++) {
        final int childIndex = start + dir * i;
        final View child = getVirtualChildAt(childIndex);
        if (child == null) {
            childLeft += measureNullChild(childIndex);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
            int childBaseline = -1;

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

            if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
                childBaseline = child.getBaseline();
            }

            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }

            //计算每个view的top
            switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
                case Gravity.TOP:
                    childTop = paddingTop + lp.topMargin;
                    if (childBaseline != -1) {
                        childTop += maxAscent[INDEX_TOP] - childBaseline;
                    }
                    break;

                case Gravity.CENTER_VERTICAL:
                    // Removed support for baseline alignment when layout_gravity or
                    // gravity == center_vertical. See bug #1038483.
                    // Keep the code around if we need to re-enable this feature
                    // if (childBaseline != -1) {
                    //     // Align baselines vertically only if the child is smaller than us
                    //     if (childSpace - childHeight > 0) {
                    //         childTop = paddingTop + (childSpace / 2) - childBaseline;
                    //     } else {
                    //         childTop = paddingTop + (childSpace - childHeight) / 2;
                    //     }
                    // } else {
                    childTop = paddingTop + ((childSpace - childHeight) / 2)
                            + lp.topMargin - lp.bottomMargin;
                    break;

                case Gravity.BOTTOM:
                    childTop = childBottom - childHeight - lp.bottomMargin;
                    if (childBaseline != -1) {
                        int descent = child.getMeasuredHeight() - childBaseline;
                        childTop -= (maxDescent[INDEX_BOTTOM] - descent);
                    }
                    break;
                default:
                    childTop = paddingTop;
                    break;
            }

            if (hasDividerBeforeChildAt(childIndex)) {
                childLeft += mDividerWidth;
            }

            childLeft += lp.leftMargin;
            setChildFrame(child, childLeft + getLocationOffset(child), childTop,
                    childWidth, childHeight);
            //计算下一个view的水平方向的起始坐标
            childLeft += childWidth + lp.rightMargin +
                    getNextLocationOffset(child);

            i += getChildrenSkipCount(child, childIndex);
        }
    }
}

https://www.jianshu.com/p/ddc9253be59f

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值