纵向布局的LinearLayout,宽度为wrap_content。当子View的宽度为match_parent时,LinearLayout和子View的最终宽度是多少

文章详细描述了LinearLayout中match_parent和wrap_content宽度设置对子View测量的影响,以及onMeasure方法的调用次数。当LinearLayout宽度为wrap_content时,子View测量模式不同;当有match_parent子View时,会额外进行两次测量以确定宽度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先写demo,看看实际表现。
布局代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:background="#ffffaa">

    <com.lihy.customviewdemo.square.MyTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="子View match_parent"
        android:background="#00ff00"/>
</LinearLayout>

MyTextView继承自AppCompatTextView,仅仅在onMeasure中加了日志

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        Log.e(TAG, "onMeasure: " +getModeString(mode));
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private String getModeString(int mode){
        if(mode == MeasureSpec.EXACTLY){
            return "EXACTLY";
        }
        if(mode == MeasureSpec.AT_MOST){
            return "AT_MOST";
        }
        return "UNSPECIFIED";
    }

实际表现如截图
在这里插入图片描述
可以看到,LinearLayout和MyTextView的宽度都取了MyTextView内容所占宽度。和MyTextView宽度设置为了"wrap_conent"效果是一样的。
打开页面后,可以看到,onMeasure打印了4次

2024-04-29 16:53:45.353 28807-28807/com.lihy.customviewdemo E/MyTextView: onMeasure: AT_MOST
2024-04-29 16:53:45.353 28807-28807/com.lihy.customviewdemo E/MyTextView: onMeasure: EXACTLY
2024-04-29 16:53:45.369 28807-28807/com.lihy.customviewdemo E/MyTextView: onMeasure: AT_MOST
2024-04-29 16:53:45.369 28807-28807/com.lihy.customviewdemo E/MyTextView: onMeasure: EXACTLY

将MyTextView的宽度改为"wrap_content"后,onMeasure打印了2次

2024-04-29 16:55:55.096 29873-29873/com.lihy.customviewdemo E/MyTextView: onMeasure: AT_MOST
2024-04-29 16:55:55.105 29873-29873/com.lihy.customviewdemo E/MyTextView: onMeasure: AT_MOST

那么当LinearLayout的宽度为wrap_content时,子View的宽度为"match_parent"与"wrap_content"表现都是"wrap_content"了吗?
那也不是!!
当LinearLayout存在2个子View时,如果子View的宽度都为"match_parent",那么LinearLayout会将子View的width当作"wrap_content",去测量子View的实际宽度,取较大的那个作为LinearLayout的宽度。然后再重新测量宽度为"match_parent"的子View。代码不再贴出,最终表现如下:
在这里插入图片描述
下面我们看LinearLayout的onMeasure方法,是怎么做到这种效果的

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

这里我们走的是measureVertical(),省略了关于height的计算,只保留了计算width的逻辑

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        final int count = getVirtualChildCount();
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        boolean matchWidth = false;
        
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //lihy:首次测量
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                    heightMeasureSpec, usedHeight);
            boolean matchWidthLocally = false;
            //lihy:注意matchWidth的定义在循环外面,但凡存在一个子View宽度为MATCH_PARENT,matchWidth标记就为true。而matchWidthLocally在循环体里面,每次都重新赋值
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                matchWidth = true;
                matchWidthLocally = true;
            }
            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            //lihy:是否当前View及之前的子View宽度全为match_parent
            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
           //lihy:当前View为match_parent时,matchWidthLocally为true,alternativeMaxWidth和margin做比较,即当前view的measureWidth无效。
            // 为false时,alternativeMaxWidth的值取子View宽度最大值
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                    matchWidthLocally ? margin : measuredWidth);
        }
        
        //lihy:可以看作这里的weightedMaxWidth就是0
        alternativeMaxWidth = Math.max(alternativeMaxWidth,
                weightedMaxWidth);
        
        //lihy:当并不是所有子View都是match_parent时,maxWidth赋值alternativeMaxWidth。
        //alternativeMaxWidth为所有子View(排除宽度为match_parent的)最大值
        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }
        maxWidth += mPaddingLeft + mPaddingRight;
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        //lihy:这里设置了自身的尺寸
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);
        //lihy:但凡存在宽度为match_parent的子View,会对所有子View重新measure
        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

看完代码后,对这个LinearLayout宽度的赋值过程就更深刻了:

1. 当子View的宽度全部为match_parent时,LinearLayout取子View宽度最大值
2. 当子View的宽度存在wrap_content时,LinearLayout取子View(不包括“宽度为match_parent的子view”)的最大值

继续看forceUniformWidth(count, heightMeasureSpec),给子View设置的宽度是多少

    private void forceUniformWidth(int count, int heightMeasureSpec) {
        // Pretend that the linear layout has an exact size.
        int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
                MeasureSpec.EXACTLY);
        for (int i = 0; i< count; ++i) {
           final View child = getVirtualChildAt(i);
           if (child != null && child.getVisibility() != GONE) {
               LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());
				//lihy:可以看到,只会对子View宽度为"MATCH_PARENT"的重新测量
               if (lp.width == LayoutParams.MATCH_PARENT) {
                   // Temporarily force children to reuse their old measured height
                   // FIXME: this may not be right for something like wrapping text?
                   int oldHeight = lp.height;
                   lp.height = child.getMeasuredHeight();

                   measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
                   lp.height = oldHeight;
               }
           }
        }
    }

从上面可以看到:

1. 只会对子View宽度为"MATCH_PARENT"的重新测量
2. 重新绘制时,对子View的约束为:size为测量好的父容器的宽度,mode为EXACTLY

结束:

  • 宽度为"wrap_content"的子View会测量1次,传给子View的mode为"AT_MOST"。
  • 宽度为"match_parent"的子View会测量2次,传给子View的mode:第1次是"AT_MOST",第2次是"EXACTLY"。

看下我们开始日志打印,onMeasure分别调用了2次和4次。也就是当前LinearLayout的方法被调用了2次。为什么外层容器的onMeasure会调用2次?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值