通过Android源码分析LinearLayout的layout_weight与weightsum对布局大小的影响

LinearLayout是Android App开发中最常用的控件之一。特别是当我们要实现几个控件平均分割一定的区域的时候,一般都会通过LinearLayout的layout_weight和weightsum组合实现。要理解layout_weight和weightsum这2个属性对LinearLayout布局大小的影响,最好还是结合源码进行分析。

源码分析

LinearLayout有水平和竖直两种方向,在源码中也有对应的2种测量方法:

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

这里以水平方向为例做个分析:

void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;//占用长度:所有明确指定大小元素长度总和+margin+padding
        float totalWeight = 0;//所有子元素的权重和

        final int count = getVirtualChildCount();
        //一般地,如果LinearLayout的layout_width为match_parent或者指定值,     widthmode为EXACTLY;wrap_content则为AT_MOST
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //是否是精确模式
        final boolean isExactly = widthMode == MeasureSpec.EXACTLY;
        int usedExcessSpace = 0;
        for (int i = 0; i < count; ++i) {//第一次遍历测量子元素
            final View child = getVirtualChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            totalWeight += lp.weight;//元素权重总和
            final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;//我们暂且称为“0宽权重模式”
            //LinearLayout为精确模式并且“0宽权重模式”
            if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
                if (isExactly) {
                    mTotalLength += lp.leftMargin + lp.rightMargin;
                } else {
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength +
                            lp.leftMargin + lp.rightMargin);
                }
            } else {//wrap_content情况或非“0宽权重模式”
                if (useExcessSpace) {//“0宽权重模式”
                    lp.width = LayoutParams.WRAP_CONTENT;
                }
                final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
                //第一次预测量子元素
                measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
                        heightMeasureSpec, 0);
                final int childWidth = child.getMeasuredWidth();
                if (useExcessSpace) {//“0宽权重模式”
                    lp.width = 0;
                    usedExcessSpace += childWidth;
                }
                //计算占用长度
                if (isExactly) {//精确模式
                    mTotalLength += childWidth + lp.leftMargin + lp.rightMargin
                            + getNextLocationOffset(child);
                } else {//wrap_content情况
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + childWidth + 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);//注意这里1,计算“剩余空间”
        if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;//注意这里2:如果指定了android:weightSum属性,权重和=android:weightSum指定的值
            mTotalLength = 0;
            //开始第二次测量子元素
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final float childWeight = lp.weight;
                if (childWeight > 0) {
                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);//注意这里3:计算子元素在剩余空间占用的份额
                    remainingExcess -= share;
                    remainingWeightSum -= childWeight;

                    final int childWidth;
                    if (lp.width == 0 && (!mAllowInconsistentMeasurement
                            || widthMode == MeasureSpec.EXACTLY)) {
                        childWidth = share;
                    } else {
                        childWidth = child.getMeasuredWidth() + share;
                    }
                    //根据childWidth创建childWidthMeasureSpec 
                    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);//注意这里4:真正测量子元素  
                }

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

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

从以上代码,我们可以得出:LinearLayout会对所有子元素进行2次测量:第一次预测量是为了计算“剩余空间”;第二次测量是根据weight、weightsum计算在“剩余空间”所占的份额,进而创建measurespec进行测量。

关于weightsum

从上面注意这里2 源码:

float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

我们可以得出:

  1. 未指定android:weightSum属性时,权重和=所有子控件的weight之和;
  2. 如果指定了android:weightSum属性,权重和=android:weightSum指定的值,而不是子控件weight和;

关于weight

  1. weight是针对剩余空间的分配,而不是针对LinearLayout总空间的分配;
  2. 剩余空间=总宽度-padding-(每个元素的原始宽度+margin+padding)
  3. 元素的长度=原始宽度+权重*父视图剩余空间/权重和

例子

测试手机屏幕宽度1440px, dp=4

例1

<?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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:orientation="horizontal">

    <View
        android:id="@+id/view1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@android:color/holo_blue_light" />

    <View
        android:id="@+id/view2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@android:color/holo_green_light" />

    <View
        android:id="@+id/view3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@android:color/holo_orange_light" />
</LinearLayout>

这是最常用的情况:
剩余空间=LinearLayout的总宽度,3个view均分
view1Width=view2Width=view3Width=1440/3=480

例2

view1的宽度改为100dp,其他不变

    ...
    <View
        android:id="@+id/view1"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@android:color/holo_blue_light" />
    ...

剩余空间=1440-100*4=1040
view1Width=400+1040/3=746
view2Width=view3Width=1040/3=346

例3

view1的宽度改为100dp,view2/view3宽度为match_parent或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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:orientation="horizontal">

    <View
        android:id="@+id/view1"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@android:color/holo_blue_light" />

    <View
        android:id="@+id/view2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@android:color/holo_green_light" />

    <View
        android:id="@+id/view3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@android:color/holo_orange_light" />
</LinearLayout>

剩余空间=1440-100*4-1440-1440=-1840
view1Width=400-1840/3<0 => 0
view2Width=view3Width=1440-1840/3=827

例4

view1/view2/view3宽度为match_parent或wrap_content,view1的layout_weight=2

<?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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:orientation="horizontal">

    <View
        android:id="@+id/view1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:background="@android:color/holo_blue_light" />

    <View
        android:id="@+id/view2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@android:color/holo_green_light" />

    <View
        android:id="@+id/view3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@android:color/holo_orange_light" />
</LinearLayout>

剩余空间=1440-1440-1440-1440=-2880
view1Width=1440-2880*2/4 = 0
view2Width=view3Width=1440-2880/4=720

另外,提示大家一点的是:可以通过Android Studio的Layout Inspector查看控件的大小
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值