LinearLayout的weight

一、weight是怎么计算的

使用LinearLayout的时候,一定会遇到weight怎么使用的问题。

简单来讲,假设LinearLayout为竖直方向

子View 的高度 = 子View 的实际高度 + 分配到的高度

实际高度:是该View的height,例如:在xml中设置的android:layout_height=”8dp”(注意这里设置的不是wrap_content或者match_parent)
分配到的高度 : (LinearLayout自身的高度 - 所有子View的总高度) / weight总和 * 该子View的weight

LinearLayout自身的高度 - 所有子View的总高度 的含义为:LinearLayout的可分配高度

所以简单来说,子View的高度 = 自身高度 + LinearLayout分配给的高度。LinearLayout是用自身可分配的高度,分成“weight总和”份,然后再按照每个子View的weight值分配。

二、例子

1、先看没加weight的时候的样子:

<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" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="aaaaaaaaaaaaaaa"
        android:background="#ff0000" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="bbb"
        android:background="#00ff00" />
</LinearLayout>

这里写图片描述

可以看到,第一个 TextView 的宽度为 15个a 的宽度,第二个 TextView 的宽度为 3个b 的宽度。

2、添加weight:

<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" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="aaaaaaaaaaaaaaa"
        android:layout_weight="1"
        android:background="#ff0000" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="bbb"
        android:layout_weight="1"
        android:background="#00ff00" />
</LinearLayout>

这里写图片描述

这就是大家最常遇到的问题,就是明明 weight 设置的一样,但是实际上的效果并不是均分。

为什么会这样呢?
看第一张图(也就是没加weight的那张图),图中,第一行,除去红色和绿色的部分是:LinearLayout除去 所有 子View 的总宽度 之后剩下的宽度,这部分才是使用weight分配的部分。
所以,
第一个TextView的总宽度是:第一个TextView的实际宽度为15个a的宽度 + LinearLayout剩余部分的一半
第二个TextView的总宽度是:第二个TextView的实际宽度为3个b的宽度 + LinearLayout剩余部分的一半

于是,虽然weight相同,但是不均分。

3、实现均分:

<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" >

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="aaaaaaaaaaaaaaa"
        android:layout_weight="1"
        android:background="#ff0000" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="bbb"
        android:layout_weight="1"
        android:background="#00ff00" />
</LinearLayout>

这里写图片描述

其实想要均分的方法很简单,就是把每个 子View 的 layout_width 置为 0dp 即可。

为什么这样就可以了呢?
因为 所有子View 的 width 都为 0dp,所以,LinearLayout可分配的总宽度为全部宽度,即屏幕总宽度。
又因为 所有子View 的 总weight值为2,所以,每份weight可以分到屏幕总宽度的一半。
所以,
第一个TextView的总宽度是:0 + LinearLayout剩余部分的一半(屏幕的一半)
第二个TextView的总宽度是:0 + LinearLayout剩余部分的一半(屏幕的一半)

于是,子View 的 layout_width 置为 0dp,均分了。

4、子View 的 layout_width 置为 match_parent

<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" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="aaaaaaaaaaaaaaa"
        android:layout_weight="1"
        android:background="#ff0000" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="bbb"
        android:layout_weight="1"
        android:background="#00ff00" />
</LinearLayout>

这里写图片描述

可以看到,也可以均分。

这里的实现与 layout_width 置为 0dp 是不同的,虽然效果是一样的,可是过程不同。

因为 所有子View 的 layout_width 都为 match_parent,子View的宽度 = LinearLayout的宽度 = 屏幕宽度

所以,LinearLayout可分配的总宽度为:
自身宽度 - 所有子View的宽度和
= 屏幕宽度 - 屏幕宽度 * 2(乘以2 是因为 2个子View 宽度都为屏幕宽度)
= -屏幕宽度

又因为 所有子View 的 总weight值为2,所以,每份weight可以分到 负的屏幕总宽度的一半。
所以,
第一个TextView的总宽度是:屏幕宽度 + LinearLayout剩余部分的一半(负的屏幕的一半) = 屏幕的一半
第二个TextView的总宽度是:屏幕宽度 + LinearLayout剩余部分的一半(负的屏幕的一半) = 屏幕的一半

于是,子View 的 layout_width 置为 match_parent,均分了。

5、验证子View 的 layout_width 置为 match_parent 与 0dp 的效果

先验证 0dp:

<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" >

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="aaaaaaaaaaaaaaa"
        android:layout_weight="1"
        android:background="#ff0000" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="bbb"
        android:layout_weight="2"
        android:background="#00ff00" />
</LinearLayout>

这里写图片描述

因为 所有子View 的 width 都为 0dp,所以,LinearLayout可分配的总宽度为全部宽度,即屏幕总宽度。
又因为 所有子View 的 总weight值为3,所以,每份weight可以分到屏幕总宽度的1/3。
所以,
第一个TextView的总宽度是:0 + LinearLayout剩余部分的1/3(屏幕的1/3) * 1(weight = 1) = 屏幕的 1/3
第二个TextView的总宽度是:0 + LinearLayout剩余部分的1/3(屏幕的1/3) * 2(weight = 2) = 屏幕的 2/3

再验证match_parent:

<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" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="aaaaaaaaaaaaaaa"
        android:layout_weight="1"
        android:background="#ff0000" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="bbb"
        android:layout_weight="2"
        android:background="#00ff00" />
</LinearLayout>

这里写图片描述

因为 所有子View 的 layout_width 都为 match_parent,子View的宽度 = LinearLayout的宽度 = 屏幕宽度

所以,LinearLayout可分配的总宽度为:
自身宽度 - 所有子View的宽度和
= 屏幕宽度 - 屏幕宽度 * 2(乘以2 是因为 2个子View 宽度都为屏幕宽度)
= -屏幕宽度

又因为 所有子View 的 总weight值为3,所以,每份weight可以分到 负的屏幕总宽度的1/3。
所以,
第一个TextView的总宽度是:屏幕宽度 + LinearLayout剩余部分的1/3(负的屏幕的1/3) * 1(weight = 1) = 屏幕的2/3
第二个TextView的总宽度是:屏幕宽度 + LinearLayout剩余部分的1/3(负的屏幕的1/3) * 2(weight = 2) = 屏幕的1/3

有了以上的例子,想必已经知道 LinearLayout 的 weight 的计算方式了,接下来看看源码中是怎么计算的。

三、源码中 weight 的计算

下面源码只截取了计算 weight 的一小部分,LinearLayout为水平的,计算的是width

        // widthSize 为整个LinearLayout的宽度,mTotalLength 为所有child测量出的宽度和,delta为可分配宽度
        int delta = widthSize - mTotalLength;

        // totalWeight 是所有child的weight的和
        if (delta != 0 && totalWeight > 0.0f) {

            // weightSum一般等于totalWeight
            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
            ......

            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分配到的宽度
                    int share = (int) (childExtra * delta / weightSum);

                    // 减去现在的weight
                    weightSum -= childExtra;

                    // 减去现在的宽度
                    delta -= share;

                    // 计算子View高度上的MeasureSpec
                    final int childHeightMeasureSpec = getChildMeasureSpec(
                            heightMeasureSpec,
                            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin,
                            lp.height);

                    if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) {
                        // 如果是 wrap_content 或者 match_parent,lp.width不为0,会执行这里

                        // 宽度 = 自身宽度 + 分配宽度
                        int childWidth = child.getMeasuredWidth() + share;

                        if (childWidth < 0) {
                            childWidth = 0;
                        }

                        child.measure(
                            MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
                            childHeightMeasureSpec);
                    } else {
                        // child的width为0dp时,执行else

                        // 宽度 = 分配宽度
                        child.measure(MeasureSpec.makeMeasureSpec(
                                share > 0 ? share : 0, MeasureSpec.EXACTLY),
                                childHeightMeasureSpec);
                    }

                    childState = combineMeasuredStates(childState,
                            child.getMeasuredState() & MEASURED_STATE_MASK);
                }

                ......
            }

            ......
        }

原创文章,转载请注明出处,谢谢~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值