美文网首页
Linearlayout layout_weight分析

Linearlayout layout_weight分析

作者: castlet | 来源:发表于2023-12-09 23:17 被阅读0次

现象

基本布局和样式

不同设置的情况

属性(只列出和基础布局不同的属性) 样式                     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

相关文章

网友评论

      本文标题:Linearlayout layout_weight分析

      本文链接:https://www.haomeiwen.com/subject/fsjugdtx.html