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 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui