• 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的尺寸
    具体数值:如100dp或100px
    AT_MOST表示View的大小可以是一个指定的最大值,MeasureSpec中的size是一个上限值,View的大小会根据内容自动调整不会超过size值wrap_content:自适应大小

    常用API

    // 获取测量模式
    int specMode = MeasureSpec.getMode(measureSpec)
    
    // 获取测量大小
    int specSize = MeasureSpec.getSize(measureSpec)
    
    // 通过Mode和Size生成新的SpecMode
    int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    MeasureSpec源码分析

    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);
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    getChildMeasureSpec源码分析

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

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

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    总结

    在这里插入图片描述

    以子View为标准,总结:

    子View的LayoutParams子View的MeasureSpec
    具体数值测量模式:EXACTLY
    测量尺寸:自身的具体数值
    match_parent测量模式:父View的测量模式
    如果父View的测量模式为EXACTLY,则测量大小:父View的剩余空间;
    如果父View的测量模式为AT_MOST,则测量大小:不超过父View的剩余空间
    wrap_content测量模式:AT_MOST
    测量尺寸:不超过父View的剩余空间

    在这里插入图片描述

  • 相关阅读:
    Go 语言进阶 - 工程进阶
    单片机-LED介绍
    Win7下安装VS2017心路历程
    [附源码]Python计算机毕业设计Django工程施工多层级管理架构
    剑指Offer面试题解总结21-30
    深度学习中的图像处理(基本介绍+示例代码)
    fiddler抓包番外————了解工具栏
    web前端面试-- 在 JavaScript 中 bind , apply 和 call 的区别
    kworker隔离绑定
    13 万字 C 语言从入门到精通保姆级教程2021 年版
  • 原文地址:https://blog.csdn.net/qq_14876133/article/details/133358605