android之layout_weight属性

原创 2017年09月18日 17:52:00

我们知道在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。 

MTK启动短信优化闪屏(闪白屏或者黑屏)

Activity启动的时候需要一段时间去初始化,这个时候就会有一定的延迟,为了提高用户体验,Android引入了StartingWindow来对这个过程进行过度,在真正的Activity进行初始化的时...

高通android 7.0短信发送流程

ComposeMessageActivity.java sendMessage mWorkingMessage.setWorkingMessageSub(mSelectedSubId);设置s...

安卓开发技巧一:深入理解Android布局中Layout_weight的属性

今天开始将要为大家介绍一些安卓开发过程将要用到的一些技巧,这些技巧全部来自网络搜集,或者自己在企业做项目的时候总结出来的,利用这些技巧将会对我们开发带来非常方便的便捷性。 先来记录一下这一段时间的技巧...

Android中设置半个屏幕大小且居中的按钮布局 (layout_weight属性)

先看如下布局 :  上图中,按钮的大小为屏幕的一半,然后居中显示在布局中央,每个人心中都有自己的答案,看看我的方法吧,布局布局xml如下 :  ...

android中如何在代码中直接设置View的layout_weight属性

android中如何在代码中直接设置View的layout_weight属性 0人收藏此文章, 我要收藏发表于9个月前(2012-12-03 17:45) , 已有767次阅读 ,共0个评论 t...
  • Moonant
  • Moonant
  • 2013年09月15日 15:50
  • 1857

使用android:layout_weight属性消除视图中的空白

layout_weight 就是先按照控件声明的尺寸进行分配,然后剩下的尺寸按weight进行分配。也就是一个控件的尺寸=控件声明的尺寸+(父控件-子控件声明的总尺寸)X(子控件权重/父控件声明的总权...
  • heverny
  • heverny
  • 2016年04月06日 12:20
  • 450

详解 Android Views 元素的 layout_weight 属性

所有View(视图)元素中都有一个XML属性android:layout_weight,其值为0,1,2,3...等整数值。使用了之后,其对应界面中的元素比例就会发生变化,变大或者变小。layout_...

android layout-weight属性

The weight value is a number that specifies the amount of remaining space each view should consume, ...

Android:layout—-weight属性

zhuan 最近写Demo,突然发现了Layout_weight这个属性,发现网上有很多关于这个属性的有意思的讨论,可是找了好多资料都没有找到一个能够说的清楚...
  • Mr__ren
  • Mr__ren
  • 2014年06月18日 10:09
  • 439

android:layout_weight属性分析

LinearLayout支持对其子View设置layout_weight属性,用来给子View分配自身剩余布局空间。此属性的类型为浮点型,默认值为0。      对于布局剩余空间,结合下面一个简单的...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:android之layout_weight属性
举报原因:
原因补充:

(最多只允许输入30个字)