Linearlayout layout_weight分析

现象

基本布局和样式

不同设置的情况

属性(只列出和基础布局不同的属性) 样式                     TextView的onMeasure执行次数
和基础布局一致
textA和TextB分别只测量1次
textB
-android:layout_weight="1"
textA测量1次
textB测量2次
textA
- android:layout_weight="1"
textB
- android:layout_weight="1"
- textA测量2次
- textB测量2次
textA
- - android:layout_weight="0"
textB
- - android:layout_weight="0"
- textA测量1次
- textB测量1次
textA
- - - android:layout_height="0dp"
- android:layout_weight="1"
textB
- android:layout_height="0dp"
- android:layout_weight="1"
- textA测量1次
- textB测量1次
textA
- android:layout_height="100dp"
- - android:layout_weight="1"
textB
- android:layout_height="0dp"
- android:layout_weight="1"
- textA测量2次
- textB测量1次
Linearlayout
-layout_height="wrap_content"
textA
- android:layout_height="100dp"
- - android:layout_weight="1"
textB
- android:layout_height="0dp"
- android:layout_weight="1"
- textA测量2次
- textB测量2次

结论

  • LinearLayout的高度为wrap_content,且设置了layout_weight大于1的子view,会测量两次
  • LinearLayout的高度为固定值或match_parent,且子view的layout_weight大于1并且layout_height不等于0,会测量两次

LinearLayout垂直布局下的测量流程

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {

    final int count = getVirtualChildCount();
    final int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
    
    boolean skippedMeasure = false;

    // 记录lp.height == 0 && lp.weight > 0 的子view第一次测量后需要的空间
    int consumedExcessSpace = 0;

    int nonSkippedChildCount = 0;

    // See how tall everyone is. Also remember max width.
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);
        final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
        totalWeight += lp.weight;
        // 如果子view的height是0,并且weight>0,则useExcessSpace为true
        final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
        if (heightMode == View.MeasureSpec.EXACTLY && useExcessSpace) {
            // Optimization: don't bother measuring children who are only
            // laid out using excess space. These views will get measured
            // later if we have space to distribute.
            // 如果linearLayout的高度是EXACTLY,并且useExcessSpace,则省略一次测量
            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
            skippedMeasure = true;
        } else {
            if (useExcessSpace) {
                // The heightMode is either UNSPECIFIED or AT_MOST, and
                // this child is only laid out using excess space. Measure
                // using WRAP_CONTENT so that we can find out the view's
                // optimal height. We'll restore the original height of 0
                // after measurement.
                // 如果如果linearLayout的高度不是EXACTLY,且子view的height是0,并且weight>0
                // 此时将子view的height改为WRAP_CONTENT,并在后续进行测量。
                // 分析:因为linearLayout的高度不是EXACTLY,因此需要对子view进行测量,否则无法确定自身的高度
                lp.height = LinearLayout.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).
            final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
            // 测量子view
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                    heightMeasureSpec, usedHeight);

            final int childHeight = child.getMeasuredHeight();
            if (useExcessSpace) {
                // Restore the original height and record how much space
                // we've allocated to excess-only children so that we can
                // match the behavior of EXACTLY measurement.
                lp.height = 0;
                // consumedExcessSpace会在后面根据weight分配给各个子view
                consumedExcessSpace += childHeight;
            }

            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                    lp.bottomMargin + getNextLocationOffset(child));

        }

    }

    // Add in our padding
    mTotalLength += mPaddingTop + mPaddingBottom;

    int heightSize = mTotalLength;

    // Check against our minimum height
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

    // Reconcile our calculated size with the heightMeasureSpec
    // 根据所有子view高度,还有自己的高度限制,来决定自己的高度
    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
    heightSize = heightSizeAndState & 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.
    // 计算还有多少剩余空间,这里需要注意consumedExcessSpace字段  
    int remainingExcess = heightSize - mTotalLength
            + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
    if (skippedMeasure
            || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
        float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

        mTotalLength = 0;

        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);


            final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
            final float childWeight = lp.weight;
            if (childWeight > 0) {
                final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                remainingExcess -= share;
                remainingWeightSum -= childWeight;

                final int childHeight;
                if (mUseLargestChild && heightMode != View.MeasureSpec.EXACTLY) {
                    childHeight = largestChildHeight;
                } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                        || heightMode == View.MeasureSpec.EXACTLY)) {
                    // This child needs to be laid out from scratch using
                    // only its share of excess space.
                    // 对于
                    childHeight = share;
                } else {
                    // This child had some intrinsic height to which we
                    // need to add its share of excess space.
                    // 如果view的高度不是0(即固定值或者wrap_content,已经进行过一次测量了)
                    childHeight = child.getMeasuredHeight() + share;
                }
                // 根据计算出的子view高度,构造薪的measureSpec并重新测量
                final int childHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
                        Math.max(0, childHeight), View.MeasureSpec.EXACTLY);
                final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                        lp.width);
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    } 
}

分析一

  • LinearLayout: 固定高度200dp
  • TextA:不设置layout_weight
  • TextB: android:layout_weight="1"


    image.png
  1. TextA和TextB进行第一测量,并计算获取mTotalLength
  2. 计算linearLayout的高度heightSize
  3. 计算remainingExcess,因为linearLayout的高度heightSize是大于mTotalLength的,因此remainingExcess > 0
int remainingExcess = heightSize - mTotalLength
            + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);

4.因为TextB设置了layout_weight,因此根据如下方式计算剩余空间,并将剩余空间和textB第一次测量的高度相加,作为新的高度,并重新进行测量流程。因此TextB一共执行了measuer流程。

// 按照weight权重分配空间
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
remainingExcess -= share;
remainingWeightSum -= childWeight;

final int childHeight;
if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
    childHeight = largestChildHeight;
} else if (lp.height == 0 && (!mAllowInconsistentMeasurement
        || heightMode == MeasureSpec.EXACTLY)) {
    // This child needs to be laid out from scratch using
    // only its share of excess space.
    childHeight = share;
} else {
    // This child had some intrinsic height to which we
    // need to add its share of excess space.
    // 走此处逻辑,计算最终的高度
    childHeight = child.getMeasuredHeight() + share;
}
// 根据计算出的子view高度,构造新的measureSpec并重新测量
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
        Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
        lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

分析二

  • LinearLayout: 固定高度200dp
  • textA
    • android:layout_height="0dp"
    • android:layout_weight="1"
  • textB
    • android:layout_height="0dp"
    • android:layout_weight="1"


      image.png

1.TextA和TextB不走第一次测量流程,mTotalLength的值为0

// TextA和TextB的useExcessSpace都是true
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
    // Optimization: don't bother measuring children who are only
    // laid out using excess space. These views will get measured
    // later if we have space to distribute.
    // 因为Linearlayout的高度是固定值,即heightMode == MeasureSpec.EXACTLY,因此TextA和TextB均走此处逻辑,不会进行第一次测量
    final int totalLength = mTotalLength;
    mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
    skippedMeasure = true;
}
  1. 计算linearLayout的高度heightSize
  2. 计算remainingExcess,因为mTotalLength的值为0(如果Linearlayout设置了padding,则mTotalLength的值需加上上下padding的大小,但不影响分析),因此remainingExcess = linearLayout的高度
  3. 将剩余空间按照TextA和TextB的weight权重分配大小,并执行measure流程。因此这种情况下TextA和TextB只走一次measure流程。
// 按照weight权重分配空间
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
remainingExcess -= share;
remainingWeightSum -= childWeight;

final int childHeight;
if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
    childHeight = largestChildHeight;
} else if (lp.height == 0 && (!mAllowInconsistentMeasurement
        || heightMode == MeasureSpec.EXACTLY)) {
    // This child needs to be laid out from scratch using
    // only its share of excess space.
    // 走此处逻辑,计算最终的高度
    childHeight = share;
} else {
    // This child had some intrinsic height to which we
    // need to add its share of excess space.
    childHeight = child.getMeasuredHeight() + share;
}
// 根据计算出的子view高度,构造新的measureSpec并重新测量
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
        Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
        lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

分析三

  • LinearLayout: 固定高度200dp
  • textA
    • android:layout_height="100dp"
    • android:layout_weight="1"
  • textB
    • android:layout_height="0dp"
    • android:layout_weight="1"


      image.png

      1.TextA设置了weight=1,且layout_height>0,因此先进行一次measure
      2.TextB满足如下条件,因此,先不进行测量。因此mTotalLength的值为TextA的测量高度

// TextB的useExcessSpace是true
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
    // Optimization: don't bother measuring children who are only
    // laid out using excess space. These views will get measured
    // later if we have space to distribute.
    // 因为Linearlayout的高度是固定值,即heightMode == MeasureSpec.EXACTLY,因此TextA和TextB均走此处逻辑,不会进行第一次测量
    final int totalLength = mTotalLength;
    mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
    skippedMeasure = true;
}

计算linearLayout的高度heightSize

  1. 计算remainingExcess,因为mTotalLength的值为TextA的测量高度,因此remainingExcess = linearLayout的高度 - TextA的测量高度
  2. 将剩余空间按照TextA和TextB的weight权重分配大小,并分别执行measure流程。至此TextA执行了两次measure,TextB只执行了一次。注意
    • TextA因为设置了layout_height > 0,已经执行过一次测量,因此TextA的高度 = 之前测量得到的高度 + 按照权重分配的剩余空间。因此TextA的高度是高于TextB的。
    • TextB的高度 = 按照权重分配的剩余空间
// 按照weight权重分配空间
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
remainingExcess -= share;
remainingWeightSum -= childWeight;

final int childHeight;
if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
    childHeight = largestChildHeight;
} else if (lp.height == 0 && (!mAllowInconsistentMeasurement
        || heightMode == MeasureSpec.EXACTLY)) {
    // This child needs to be laid out from scratch using
    // only its share of excess space.
    // TextB 走此处逻辑,计算最终的高度
    childHeight = share;
} else {
    // This child had some intrinsic height to which we
    // need to add its share of excess space.
    // TextA 走此处逻辑,计算最终的高度
    childHeight = child.getMeasuredHeight() + share;
}
// 根据计算出的子view高度,构造新的measureSpec并重新测量
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
        Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
        lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

分析四

  • linearlayout
    • layout_height="wrap_content"
  • textA
    • android:layout_height="100dp"
    • android:layout_weight="1"
  • textB
    • android:layout_height="0dp"
    • android:layout_weight="1"
image.png

1.TextA设置了weight=1,且layout_height>0,因此先进行一次measure
2.TextB按照如下流程进行测量,并设置consumedExcessSpace

// TextB的useExcessSpace=true
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
    // Optimization: don't bother measuring children who are only
    // laid out using excess space. These views will get measured
    // later if we have space to distribute.
    final int totalLength = mTotalLength;
    mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
    skippedMeasure = true;
} else {
    if (useExcessSpace) {
        // 因为LinearLayout的高度不是EXACTLY,因此走此处逻辑,
        // 注意: 这里设置height = LayoutParams.WRAP_CONTENT,用该参数走后续的测量流程
        lp.height = LayoutParams.WRAP_CONTENT;
    }

    // 执行TextB的测量
    measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
            heightMeasureSpec, usedHeight);

    final int childHeight = child.getMeasuredHeight();
    if (useExcessSpace) {
        lp.height = 0;
        // 设置consumedExcessSpace的值
        consumedExcessSpace += childHeight;
    }

3.计算LinearLayout的高度,高度heightSize等于TextA和TextB的高度之和,即和mTotalLength相等

// mTotalLength是之前计算的TextA和TextB的高度之和,注意,TextB是按照WRAP_CONTENT的方式测量的
int heightSize = mTotalLength; 
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                // LinearLayout的高度为wrap_content,对应MeasureSpec.AT_MOST, 因此走到此处逻辑,
                // 即LinearLayout的高度为传入的size
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}
  1. 计算剩余空间remainingExcess,因为第2步执行TextB的测量的时候,consumedExcessSpace被赋值,等于TextB的测量高度,因此remainingExcess > 0
int remainingExcess = heightSize - mTotalLength
        + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
  1. 将剩余空间按照TextA和TextB的weight权重分配大小,并分别执行measure流程。至此TextA和TextB分别执行了两次measure流程。
    • 因为LinearLayout的高度是wrap_content,需要子view执行完测量后才能确定自己的大小,这是TextB需要执行第一次测量流程的原因。
    • TextA因为设置了layout_height > 0,因此TextA的高度 = 之前测量得到的高度 + 按照权重分配的剩余空间。因此TextA的高度是高于TextB的。
    • TextB的高度 = 按照权重分配的剩余空间
// 按照weight权重分配空间
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
remainingExcess -= share;
remainingWeightSum -= childWeight;

final int childHeight;
if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
    childHeight = largestChildHeight;
} else if (lp.height == 0 && (!mAllowInconsistentMeasurement
        || heightMode == MeasureSpec.EXACTLY)) {
    // This child needs to be laid out from scratch using
    // only its share of excess space.
    // TextB 走此处逻辑,计算最终的高度
    childHeight = share;
} else {
    // This child had some intrinsic height to which we
    // need to add its share of excess space.
    // TextA 走此处逻辑,计算最终的高度
    childHeight = child.getMeasuredHeight() + share;
}
// 根据计算出的子view高度,构造新的measureSpec并重新测量
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
        Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
        lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

问题

设置了layout_weight一定会走2次测量流程吗?

不是的,如下情况会走2次measure:

  • LinearLayout的高度为wrap_content,且layout_weight大于1的子view,会测量两次
  • LinearLayout的高度为固定值或match_parent,且子view的layout_weight大于1并且layout_height不等于0,会测量两次

如下设置,则只走一次measure:

  • LinearLayout的高度为固定值或match_parent,且子view的layout_weight大于1并且layout_height等于0
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容