android之layout_weight属性

我们知道在xml布局的时候,LinearLayout下面的子控件可以使用layout_weight属性,那么我们根据源码,来分析下这个属性的一些知识点。


首先,在系统代码attrs.xml,我们可以知道对这个的定义,还有一个跟weight有关的weightSum的定义。

<declare-styleable name="LinearLayout_Layout">
        <attr name="layout_width" />
        <attr name="layout_height" />
        <attr name="layout_weight" format="float" /> 这个就是layout_weight的定义
        <attr name="layout_gravity" />
</declare-styleable>

<declare-styleable name="LinearLayout">
<attr name="weightSum" format="float" /> weightSum是LinearLayout的属性值

然后我们在LinearLayout.java中
找到LinearLayout配对的LayoutParams,里面
public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a =
                    c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
            weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
            gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);
            a.recycle();
 }
所以从这里我们知道layout_weight是专属于LinearLayout的子控件所用,也只有在LinearLayout下才起作用。我们在xml中在LinearLayout下定义其子view,会自动为该子view生成LinearLayout.LayoutParams的布局参数的。在代码中,要显示定义。


layout_weight属性是跟控件大小相关的,属于measure,所以我们可以看下LinearLayout的onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);//我们选择水平布局来看
        }
    }

measureHorizontal代码很长,我们只看跟weight相关的代码

voidmeasureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;//LinearLayout已经被使用了的大小
        .......
        float totalWeight = 0;//LinearLayout的所有子view总的weight


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

        int usedExcessSpace = 0;

        // 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 LayoutParams lp = (LayoutParams) child.getLayoutParams();
            totalWeight += lp.weight;

            final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;//我们useExcessSpace 为false,以为lp.width是-1和-2这两种情况
            if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
                ......
            } else {
                if (useExcessSpace) {
                   .......
                }

                // 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;
                measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
                        heightMeasureSpec, 0);//第一次测量子view的大小,因为有weight,所以会有第二次测量


                final int childWidth = child.getMeasuredWidth();
                if (useExcessSpace) {
                    ......
                }


                if (isExactly) {//我们默认LinearLayout是占据屏幕宽度来分析
                    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 (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerWidth;
        }


        if (useLargestChild &&//useLargestChild 默认为false
                (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
            ......
        }


        // 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);//根据父亲的MeasureSpec参数测量LinearLayout的大小,这里是占据整个屏幕
        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//下面的为0,根据注释我们也知道,这个值就是剩余可用空间的大小
                + (mAllowInconsistentMeasurement ? 0 : usedExcessSpace);

        if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;//这里mWeightSum 为0,所以remainingWeightSum 就是刚刚遍历所有子view后所有的weight的相加值
            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) {
                    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);//第二次设置子view的大小

                }

        }
        ......//这里是测量LinearLayout的大小,不是我们这次的主题
    }


所以根据上面的源码后,我们可以这样总结:

1.layout_weight只有LinearLayout的子view可以用

2.所有有layout_weight的子view需要经历两次measure,一次是正常measure,一次是根据linearlayout最后剩余的可用空间结合layout_weight计算其share,然后和原来的大小相加

3.所以,当子view都为match_parent,最后计算后,layout_weight大的反而最后占据的空间小。而wrap_content则相反。

4.例如有两个子view A和B,都是match_parent,layout_weight分别为1,2,所以最后A的空间为1+ 1*(1 - (1+1))/(1+2) = 2/3,而B的空间则是1/3。 

阅读更多
个人分类: android
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭