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的剩余空间
相关推荐
huibin1478523691 小时前
dumpsys alarm 简介
android
柯南二号4 小时前
【Android】【设计模式】抽象工厂模式改造弹窗组件必知必会
android·设计模式·抽象工厂模式
TsengOnce10 小时前
MySQL 性能调优与 SQL 优化的核心利器
android·sql·mysql
熊大与iOS11 小时前
iOS 长截图的完美实现方案 - 附Demo源码
android·算法·ios
独行soc12 小时前
2025年渗透测试面试题总结-42(题目+回答)
android·数据库·安全·adb·面试·渗透测试·sqlite
千里马学框架13 小时前
安卓15 audio新专题发布:安卓系统手机车机音频audio子系统深入实战开发专题
android·智能手机·音视频
没有了遇见14 小时前
Android 虚拟环境之虚拟环境检测<完结版>
android
liang_jy14 小时前
Android 单元测试(二)—— 高级 Mock 技术
android·面试·单元测试