android:layout_weight权重分析

在Android的LinearLayout布局中我们常常会使用android:layout_weight属性做一些适配处理。有的时候我们用这个属性会出现很奇葩的事,按理来说按照权重比例分,总有些时候不是这样,那么这到底是为什么呢,这篇博客就给你解答。

官方解释说:

Indicates how much of the extra space in the LinearLayout will be allocated to the view
associated with these LayoutParams. Specify 0 if the view should not be stretched. Otherwise
the extra pixels will be pro-rated among all views whose weight is greater than 0.

意思是在LinearLayout中如果android:layout_weight属性不设置或者设置为“0”,那么就按照控件的大小布局;如果设置了android:layout_weight属性而且该属性大于“0”,那么LinearLayout就要再一次Measure一次(这个属性是子控件所有weight之和,源码中可以看出来),他先将每个控件按照定义的布局,然后再次measure后根据权重重新布局,后面的布局要根据LinearLayout的额外的布局大小进行布局。

他们的布局最终需要通过调用onMeasure方法,如果没有权重(默认为“0”)就按照实际的子控件大小布局,反之权重之和大于“0”就要先按照控件属性布局再进行第二次测量最终按照两次布局之和布局到UI上。(今天讨论的以android:orientation="horizontal"为例)可以用一个公式来看:

剩余可用空间可以为负数。

我们来看一下源码:

onMeasure方法如下,我们以Horizontal为例往下看:

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

    /**
     * Measures the children when the orientation of this LinearLayout is set
     * to {@link #HORIZONTAL}.
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
     *
     * @see #getOrientation()
     * @see #setOrientation(int)
     * @see #onMeasure(int, int) 
     */
    void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
        int maxHeight = 0;
        int childState = 0;
        int alternativeMaxHeight = 0;
        int weightedMaxHeight = 0;
        boolean allFillParent = true;
        float totalWeight = 0;

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

        boolean matchHeight = false;
        boolean skippedMeasure = false;

        if (mMaxAscent == null || mMaxDescent == null) {
            mMaxAscent = new int[VERTICAL_GRAVITY_COUNT];
            mMaxDescent = new int[VERTICAL_GRAVITY_COUNT];
        }

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

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

        final boolean baselineAligned = mBaselineAligned;
        final boolean useLargestChild = mUseLargestChild;
        
        final boolean isExactly = widthMode == MeasureSpec.EXACTLY;

        int largestChildWidth = Integer.MIN_VALUE;

        // See how wide everyone is. Also remember max height.
        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;
            }

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

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

            totalWeight += lp.weight;
            
            if (widthMode == MeasureSpec.EXACTLY && lp.width == 0 && lp.weight > 0) {
                // Optimization: don't bother measuring children who are going to use
                // leftover space. These views will get measured again down below if
                // there is any leftover space.
                if (isExactly) {
                    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 {
                int oldWidth = Integer.MIN_VALUE;

                if (lp.width == 0 && lp.weight > 0) {
                    // widthMode is either UNSPECIFIED or AT_MOST, and this
                    // child
                    // wanted to stretch to fill available space. Translate that to
                    // WRAP_CONTENT so that it does not end up with a width of 0
                    oldWidth = 0;
                    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).
                measureChildBeforeLayout(child, i, widthMeasureSpec,
                        totalWeight == 0 ? mTotalLength : 0,
                        heightMeasureSpec, 0);

                if (oldWidth != Integer.MIN_VALUE) {
                    lp.width = oldWidth;
                }

                final int childWidth = child.getMeasuredWidth();
                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 (mTotalLength > 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 delta = widthSize - mTotalLength;
        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
            float weightSum = 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 LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();

                float childExtra = lp.weight;
                if (childExtra > 0) {
                    // Child said it could absorb extra space -- give him his share
                    int share = (int) (childExtra * delta / weightSum);
                    weightSum -= childExtra;
                    delta -= share;

                    final int childHeightMeasureSpec = getChildMeasureSpec(
                            heightMeasureSpec,
                            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin,
                            lp.height);

                    // TODO: Use a field like lp.isMeasured to figure out if this
                    // child has been previously measured
                    if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) {
                        // child was measured once already above ... base new measurement
                        // on stored values
                        int childWidth = child.getMeasuredWidth() + share;
                        if (childWidth < 0) {
                            childWidth = 0;
                        }

                        child.measure(
                            MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
                            childHeightMeasureSpec);
                    } else {
                        // child was skipped in the loop above. Measure for this first time here
                        child.measure(MeasureSpec.makeMeasureSpec(
                                share > 0 ? share : 0, MeasureSpec.EXACTLY),
                                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);
        }
    }

方法很长,我们捡关键的说:

我们会看到一个for循环,也就是我们说的第一次measure,在这次measure中它

totalWeight += lp.weight;

获取到总共的权重,如果=0或者第一次测量,测量出所有子View的宽度的和(还没考虑layout_weight)

// 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).
measureChildBeforeLayout(child, i, widthMeasureSpec,
        totalWeight == 0 ? mTotalLength : 0,
        heightMeasureSpec, 0);
final int childWidth = child.getMeasuredWidth();
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));
}

然后按照子控件长宽布局:

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

如果≠0

就会测量第二次,首选呢,计算出额外空间

// 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 delta = widthSize - mTotalLength;

也就是 LinearLayout的宽度 - 子控件总共的宽度,然后遍历LinearLayout的子元素,如果不为null且Visibility不为GONE的话,取得它的LayoutParams,如果它的layout_weight大于0,根据weightSum与它的weight计算出分配给它的额外空间,就得到了最终的宽度:

if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
    float weightSum = 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 LinearLayout.LayoutParams lp =
                (LinearLayout.LayoutParams) child.getLayoutParams();

        float childExtra = lp.weight;
        if (childExtra > 0) {
            // Child said it could absorb extra space -- give him his share
            int share = (int) (childExtra * delta / weightSum);
            weightSum -= childExtra;
            delta -= share;

            final int childHeightMeasureSpec = getChildMeasureSpec(
                    heightMeasureSpec,
                    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin,
                    lp.height);

            // TODO: Use a field like lp.isMeasured to figure out if this
            // child has been previously measured
            if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) {
                // child was measured once already above ... base new measurement
                // on stored values
                int childWidth = child.getMeasuredWidth() + share;
                if (childWidth < 0) {
                    childWidth = 0;
                }

                child.measure(
                    MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
                    childHeightMeasureSpec);
            } else {
                // child was skipped in the loop above. Measure for this first time here
                child.measure(MeasureSpec.makeMeasureSpec(
                        share > 0 ? share : 0, MeasureSpec.EXACTLY),
                        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);
    }
}

我举个栗子

将整个LinearLayout的宽度设为 “L”,最终长度设为“l”。

没有weight属性

    match_parent

XML如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button1"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button2"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button3"/>

</LinearLayout>
效果如下:

由于没有weight属性,LinearLayout就会按照子控件的设定大小进行布局,由于第一个Button1已经充满了整个LinearLayout,Button2/3也就没有位置了,故显示不出来。

    wrap_content

再来看这个布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button1"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button2"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button3"/>

</LinearLayout>

也很好解释,首先呢Button1/2会先按照大小适应内容的形式布局好,然后Button3将会充满剩余的部分,而不是按照内容适配
再看
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button1"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button2"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button3"/>

</LinearLayout>
我将第二个设置为match_content,第一个Button1先按照控件大小布局,Button2由于是充满,那么就没有剩余空间了,Button3也就不能显示了。

带有weight属性

    match_parent

XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="button1"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button2"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button3"/>

</LinearLayout>
奇怪吧,为什么只显示了Button2??????那好,我们计算一下,由于有了weight属性,LinearLayout会measure两次,第一次给每个Button都分配了“L”的长度,第二次就要根据权重了:
每个Button分配长度:L
剩余可用空间:L - 3*L = -2L
权重比:1:0:0
权重和:1

Button1: l = L + (-2L)*(1/1) = -L
Button2: l = L + (-2L)*(0/1) = L
Button3: l = L + (-2L)*(0/1) = L
实际Button1长度为负数,那么是没有空间的,Button2分配了L,也就是充满,Button3也是L,但是已经没有空间可分配了,故只显示了Button2。
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="button1"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="button2"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button3"/>

</LinearLayout>

 
每个Button分配长度:L
剩余可用空间:L - 3*L = -2L
权重比:1:2:0
权重和:3

Button1: l = L + (-2L)*(1/3) =  1/3L
Button2: l = L + (-2L)*(2/3) = -1/3L
Button3: l = L + (-2L)*(0/3) =  L
所以Button2消失,Button3实际长度为L,但是界面显示不全而已,因为在可视化的时候已经有个Button1占用了1/3L。
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="button1"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="button2"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="3"
        android:text="button3"/>

</LinearLayout>
 
每个Button分配长度:L
剩余可用空间:L - 3*L = -2L
权重比:1:2:3
权重和:6

Button1: l = L + (-2L)*(1/6) = 2/3L
Button2: l = L + (-2L)*(2/6) = 1/3L
Button3: l = L + (-2L)*(3/6) =  0
所以咯就显示出2:1:0的形态
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="button1"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="button2"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="button3"/>

</LinearLayout>

每个Button分配长度:L
剩余可用空间:L - 3*L = -2L
权重比:1:2:2
权重和:5

Button1: l = L + (-2L)*(1/5) = 3/5L
Button2: l = L + (-2L)*(2/5) = 1/5L
Button3: l = L + (-2L)*(2/5) = 1/5L
控件不齐是因为宽度按照内容适配了。

    wrap_content

该效果不像match_content 让人感觉不合乎情理,它也是按照上述公式计算的,只是效果显示可以这样概括:先将wrap_content的View布局ok,剩余的空间按照权重分配给其他的View。
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="button1"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button2"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button3"/>

</LinearLayout>
每个Button分配长度:l'(wrap_content)
剩余可用空间:L - 3*l' = m
权重比:1:0:0
权重和:1

Button1: l = l' + (m)*(1/1) = L - 2l'
Button2: l = l' + (m)*(0/1) = l'
Button3: l = l' + (m)*(0/1) = l'

再看:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="button1"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="button2"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button3"/>

</LinearLayout>

 
每个Button分配长度:l'(wrap_content)
剩余可用空间:L - 3*l' = m
权重比:1:2:0
权重和:3

Button1: l = l' + (m)*(1/3) = 1/3L
Button2: l = l' + (m)*(2/3) = 2/3L - l'
Button3: l = l' + (m)*(0/3) = l'
继续:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="button1"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="button2"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="button3"/>

</LinearLayout>
每个Button分配长度:l'(wrap_content)
剩余可用空间:L - 3*l' = m
权重比:1:2:2
权重和:5

Button1: l = l' + (m)*(1/5) = 1/5L + 2/5*l'
Button2: l = l' + (m)*(2/5) = 2/5L - 1/5*l'
Button3: l = l' + (m)*(2/5) = 2/5L - 1/5*l'

weightSum

顺带着提一下weightSum属性,顾名思义,就是制定权重的总值,如果不指定weightSum,系统会将各个Layout_weight的值相加得出的总数作为权重总值,当然我们也可以在这里显式指定,有的时候我们想达到这个效果:
我们就可以指定weightSum属性来控制,XML如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:weightSum="2"
    android:orientation="horizontal">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="button1"/>

</LinearLayout>

那我们总结一下

没有weight属性的按照子控件实际大小分配空间,如果有该属性且大于0,首次先按照子控件大小分配并算出可用空间,第二次计算实际大小按照首次计算分配的子控件大小+剩余空间按照权重比分配的大小。对于wrap_content我们可以理解为,将既定已知的控件大小分配好后,剩余的控件按照权重比分配大小,当然实际还是那个公式,只是看起来比较像而已,如果你尝试不同比例其实效果并不如你所想,还是老老实实按照公式代吧~~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值