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的剩余空间
相关推荐
HerayChen1 小时前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野1 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing11231 小时前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小黄人软件2 小时前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio
dj15402252032 小时前
group_concat配置影响程序出bug
android·bug
周全全2 小时前
MySQL报错解决:The user specified as a definer (‘root‘@‘%‘) does not exist
android·数据库·mysql
- 羊羊不超越 -3 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
wk灬丨4 小时前
Android Kotlin Flow 冷流 热流
android·kotlin·flow
千雅爸爸4 小时前
Android MVVM demo(使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成)
android