自定义view的measureSpec是谁的mode和size

本文介绍了自定义Android View时如何处理onMeasure过程,特别是widthMeasureSpec的来源和含义。widthMeasureSpec包含mode和size两部分,mode决定了View的尺寸约束,size则指示了尺寸值。它并非直接来源于XML,而是由父视图在测量过程中传递,根据父视图的尺寸和子视图的布局参数决定。在RelativeLayout的例子中,父视图的尺寸用于帮助子View确定其match_parent等情况下的尺寸。

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

一、简单背景介绍

在自定义view(不是ViewGroup)时,需要重写父类的onMeausre(), onLayout(), onDraw():

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

看了很多书和文章,都介绍了widthMeasureSpec是什么东西。他是一个MeasureSpec,32位的int,前两位记录了他的mode,后30位记录了他的size。

mode如下:

ModeNameCool
00UNSPECIFIED0
01EXACTLY1073741824
10AT_MOST-2147483648

但是widthMeasureSpec和heightMeasureSpec是从哪儿来的?他的mode和size是在xml中定义的mode和size?还是他父亲的mode和size?

二、探索onMeasure中的widthMeasureSpec

自定义view的xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.wangxuan.customviewheight.CustomView
        android:layout_width="300px"
        android:layout_height="400px"
        android:background="@color/colorPrimary" />

</RelativeLayout>

自定义view的java代码:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpec = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpec = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        Log.e(TAG, "before wspec:" + widthSpec + ",w:" + width + ",hspec:" + heightSpec + ",h:" + height);

        if (widthSpec == MeasureSpec.EXACTLY) {
            Log.e(TAG, "width EXACTLY");
        } else if (widthSpec == MeasureSpec.AT_MOST) {
            Log.e(TAG, "width AT_MOST");
        } else {
            Log.e(TAG, "width UNSPECIFIED");
        }
        if (heightSpec == MeasureSpec.EXACTLY) {
            Log.e(TAG, "height EXACTLY");
        } else if (heightSpec == MeasureSpec.AT_MOST) {
            Log.e(TAG, "height AT_MOST");
        } else {
            Log.e(TAG, "height UNSPECIFIED");
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e(TAG, "after wspec:" + widthSpec + ",w:" + width + ",hspec:" + heightSpec + ",h:" + height);

        if (widthSpec == MeasureSpec.EXACTLY) {
            Log.e(TAG, "width EXACTLY");
        } else if (widthSpec == MeasureSpec.AT_MOST) {
            Log.e(TAG, "width AT_MOST");
        } else {
            Log.e(TAG, "width UNSPECIFIED");
        }
        if (heightSpec == MeasureSpec.EXACTLY) {
            Log.e(TAG, "height EXACTLY");
        } else if (heightSpec == MeasureSpec.AT_MOST) {
            Log.e(TAG, "height AT_MOST");
        } else {
            Log.e(TAG, "height UNSPECIFIED");
        }
    }

运行结果:

E/CustomView: before wspec:1073741824,w:300,hspec:-2147483648,h:1038
E/CustomView: width EXACTLY                                         
E/CustomView: height AT_MOST                                        
E/CustomView: after wspec:1073741824,w:300,hspec:-2147483648,h:1038 
E/CustomView: width EXACTLY                                         
E/CustomView: height AT_MOST                                        
E/CustomView: before wspec:1073741824,w:300,hspec:1073741824,h:400  
E/CustomView: width EXACTLY                                         
E/CustomView: height EXACTLY                                        
E/CustomView: after wspec:1073741824,w:300,hspec:1073741824,h:400   
E/CustomView: width EXACTLY                                         
E/CustomView: height EXACTLY                                        

三、解释

在RelativeLayout中的onMeasure中会对其所有的子view进行测量。因为RelativeLayout的水平和竖直方向都是相对的,所以在进行测量其子view的大小时,需要分水平和竖直两个方向分别测量,即先测量width,在测量height。下面只给出了测量width的源代码:

        int count = views.length;

        for (int i = 0; i < count; i++) {
            View child = views[i];
            if (child.getVisibility() != GONE) {
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                int[] rules = params.getRules(layoutDirection);

                applyHorizontalSizeRules(params, myWidth, rules);
                measureChildHorizontal(child, params, myWidth, myHeight);

                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                    offsetHorizontalAxis = true;
                }
            }
        }

可以看到,RelativeLayout在测量子view的时候,是调用了child.getLayoutParams()。而myWidth和myHeight则是RelativeLayout的width和height。所以可知自定义view的widthMeasureSpec是自身xml规定的size和mode。那传进去的父view的myWidth和myHeight实际是为了帮助子 view进行一些测量,比如match_parent时,需要通过父亲的大小来界定子 view的大小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值