Android MeasureSpec测量规格

文章目录

Android MeasureSpec测量规格

概述

MeasureSpec指View的测量规格,MeasureSpec是View的一个静态内部类。

View的MeasureSpec 是根据自身的布局参数(LayoutParams)父View的MeasureSpec共同计算出来的。

MeasureSpec组成

测量规格封装了父View对子View布局上的限制。

测量规格(MeasureSpec)是由测量模式(mode)和测量大小(size)组成,共32位整数型:

  • 高2位表示测量模式SpecMode。
  • 低30位表示测量尺寸SpecSize。

测量模式(SpecMode)共3种:

模式 说明 场景
UNSPECIFIED 表示View的大小没有限制,MeasureSpec中的size可以为任意值 系统内部,如:ListView、ScrollView
EXACTLY 表示View的大小已经确定,MeasureSpec中的size是一个精确值 match_parent:强制使View的尺寸扩展至父View的尺寸
EXACTLY 表示View的大小已经确定,MeasureSpec中的size是一个精确值 具体数值:如100dp或100px
AT_MOST 表示View的大小可以是一个指定的最大值,MeasureSpec中的size是一个上限值,View的大小会根据内容自动调整不会超过size值 wrap_content:自适应大小

常用API

java 复制代码
// 获取测量模式
int specMode = MeasureSpec.getMode(measureSpec)

// 获取测量大小
int specSize = MeasureSpec.getSize(measureSpec)

// 通过Mode和Size生成新的SpecMode
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);

MeasureSpec源码分析

java 复制代码
public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    public static final int EXACTLY     = 1 << MODE_SHIFT;

    public static final int AT_MOST     = 2 << MODE_SHIFT;

    //根据尺寸和测量模式生成一个MeasureSpec
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    //获取测量模式
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    //获取测量尺寸
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }

    //调整MeasureSpec大小
    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        int size = getSize(measureSpec);
        if (mode == UNSPECIFIED) {
            return makeMeasureSpec(size, UNSPECIFIED);
        }
        size += delta;
        if (size < 0) {
            Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                  ") spec: " + toString(measureSpec) + " delta: " + delta);
            size = 0;
        }
        return makeMeasureSpec(size, mode);
    }  
}

getChildMeasureSpec源码分析

View的MeasureSpec是根据View自身的LayoutParams和父View的MeasureSpec决定的。

MeasureSpec的计算逻辑封装在 ViewGroup#getChildMeasureSpce() 方法中。

java 复制代码
public abstract class ViewGroup{

    /**	
    * spec:父View的测量规格
	* padding:父容器的已用空间(父View的padding和子View的margin)
	* childDimension:子View的尺寸(布局参数)
	**/
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {    
        //获取父View的测量模式
        int specMode = MeasureSpec.getMode(spec);     
        //获取父View的测量尺寸
        int specSize = MeasureSpec.getSize(spec); 

        //计算父View的剩余空间
        int size = Math.max(0, specSize - padding);  

        //子View期望的尺寸和模式(需要计算)  
        int resultSize = 0;  
        int resultMode = 0;  

        //如下通过父View的MeasureSpec和子View的LayoutParams计算过程:

        switch (specMode) {                 
                //当父View的模式为EXACTLY时,也就是父View设置为match_parent或具体数值时。
            case MeasureSpec.EXACTLY:  
                if (childDimension >= 0) {                           
                    //如果子View有具体数值,则子View的尺寸为自身的值,模式为EXACTLY
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {                     
                    //如果子View为match_parent时,则子View的尺寸为父View的剩余空间大小,模式为EXACTLY
                    resultSize = size;  
                    resultMode = MeasureSpec.EXACTLY;                          
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {       
                    //如果子View为wrap_content时,则子View的尺寸为父View的剩余空间大小,模式为AT_MOST
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                }  
                break;  

                //父View的模式为AT_MOST时,也就是wrap_content。
            case MeasureSpec.AT_MOST:  
                if (childDimension >= 0) {               
                    //如果子View有具体数值,则子View的尺寸为自身的值,模式为EXACTLY
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {                     
                    //如果子View为match_parent时,则子View的尺寸为父View的剩余空间大小,模式为AT_MOST
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {                 
                    //如果子View为wrap_content时,则子View的尺寸为父View的剩余空间大小,模式为AT_MOST
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                }  
                break;  

                //当父View的模式为UNSPECIFIED时,父View不对子View限制,常用于系统空间,如ListView、ScrollView等。
            case MeasureSpec.UNSPECIFIED:       
                if (childDimension >= 0) {  
                    //如果子View有具体数值,则子View的尺寸为自身的值,模式为EXACTLY
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {                  
                    //如果子View为match_parent时,则子View的尺寸为父View的剩余空间大小,模式为UNSPECIFIED
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    //如果子View有具体数值,则子View的尺寸为0,模式为UNSPECIFIED
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                }  
                break;  
        }  
        
        //计算子View的测量规格
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
    }  

    /**	
     * child:子View
     * parentWidthMeasureSpec:父View的宽的测量规格
     * widthUsed:父View在宽上的已使用空间
     * parentHeightMeasureSpec:父View的高的测量规格
     * heightUsed:父View在高上的已使用空间
     **/
    protected void measureChildWithMargins(View child,
                                           int parentWidthMeasureSpec, int widthUsed,
                                           int parentHeightMeasureSpec, int heightUsed) {
        //获取子View的布局参数
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        //获取子View的宽的测量规格
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                                                              mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                                                              + widthUsed, lp.width);
        //获取子View的高的测量规格
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                                                               mPaddingTop + mPaddingBottom + 		lp.topMargin + lp.bottomMargin
                                                               + heightUsed, lp.height);
        //测量子View
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
}

总结

以子View为标准,总结:

子View的LayoutParams 子View的MeasureSpec
具体数值 测量模式:EXACTLY 测量尺寸:自身的具体数值
match_parent 测量模式:父View的测量模式 如果父View的测量模式为EXACTLY,则测量大小:父View的剩余空间; 如果父View的测量模式为AT_MOST,则测量大小:不超过父View的剩余空间
wrap_content 测量模式:AT_MOST 测量尺寸:不超过父View的剩余空间
相关推荐
小比卡丘1 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭2 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss3 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.4 小时前
数据库语句优化
android·数据库·adb
GEEKVIP6 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
model20058 小时前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏6898 小时前
Android广播
android·java·开发语言
与衫9 小时前
掌握嵌套子查询:复杂 SQL 中 * 列的准确表列关系
android·javascript·sql
500了15 小时前
Kotlin基本知识
android·开发语言·kotlin
人工智能的苟富贵16 小时前
Android Debug Bridge(ADB)完全指南
android·adb