38

Android-MeasureSpec那些事

 5 years ago
source link: https://tryenough.com/android-MeasureSpec?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android系统控件无法满足我们的需求,因此有必要自定义View。具体方法参见官方开发文档: http://developer.android.com/guide/topics/ui/custom-components.html

MeasureSpec的简介

MesureSpec可以理解为测量View大小的依据。它由一个32位的int值组成,前两位表示 测量模式 ,后30位表示 大小值

EJzYrym.png!web

测量模式(Mode)的类型有3种:UNSPECIFIED、EXACTLY 和

AT_MOST。

IFvU7ne.png!web

Measure源码分析

public class MeasureSpec {

        // 进位大小 = 2的30次方
        // int的大小为32位,所以进位30位 = 使用int的32和31位做标志位
        private static final int MODE_SHIFT = 30;  

        // 运算遮罩:0x3为16进制,10进制为3,二进制为11
        // 3向左进位30 = 11 00000000000(11后跟30个0)  
        // 作用:用1标注需要的值,0标注不要的值。因1与任何数做与运算都得任何数、0与任何数做与运算都得0
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  

        // UNSPECIFIED的模式设置:0向左进位30 = 00后跟30个0,即00 00000000000
        // 通过高2位
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;  

        // EXACTLY的模式设置:1向左进位30 = 01后跟30个0 ,即01 00000000000
        public static final int EXACTLY = 1 << MODE_SHIFT;  

        // AT_MOST的模式设置:2向左进位30 = 10后跟30个0,即10 00000000000
        public static final int AT_MOST = 2 << MODE_SHIFT;  

        /**
          * makeMeasureSpec()方法
          * 作用:根据提供的size和mode得到一个详细的测量结果,即measureSpec
          **/ 
            public static int makeMeasureSpec(int size, int mode) {  

                return size + mode;  
            // measureSpec = size + mode;此为二进制的加法 而不是十进制
            // 设计目的:使用一个32位的二进制数,其中:32和31位代表测量模式(mode)、后30位代表测量大小(size)
            // 例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100  

            }  

        /**
          * getMode()方法
          * 作用:通过measureSpec获得测量模式(mode)
          **/    

            public static int getMode(int measureSpec) {  

                return (measureSpec & MODE_MASK);  
                // 即:测量模式(mode) = measureSpec & MODE_MASK;  
                // MODE_MASK = 运算遮罩 = 11 00000000000(11后跟30个0)
                //原理:保留measureSpec的高2位(即测量模式)、使用0替换后30位
                // 例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值

            }  
        /**
          * getSize方法
          * 作用:通过measureSpec获得测量大小size
          **/       
            public static int getSize(int measureSpec) {  

                return (measureSpec & ~MODE_MASK);  
                // size = measureSpec & ~MODE_MASK;  
               // 原理类似上面,即 将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size  
            } 

    }

MeasureSpec值的计算

子view的大小(MeasureSpec值)由父view的MeasureSpec值 和 子view的LayoutParams属性 共同决定,具体计算逻辑封装在getChildMeasureSpec()里.

/**
  * 源码分析:getChildMeasureSpec()
  * 作用:根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
  * 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams属性 共同决定
  **/

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  

         //参数说明
         * @param spec 父view的详细测量值(MeasureSpec) 
         * @param padding view当前尺寸的的内边距和外边距(padding,margin) 
         * @param childDimension 子视图的布局参数(宽/高)

            //父view的测量模式
            int specMode = MeasureSpec.getMode(spec);     

            //父view的大小
            int specSize = MeasureSpec.getSize(spec);     

            //通过父view计算出的子view = 父大小-边距(父要求的大小,但子view不一定用这个值)   
            int size = Math.max(0, specSize - padding);  

            //子view想要的实际大小和模式(需要计算)  
            int resultSize = 0;  
            int resultMode = 0;  

            //通过父view的MeasureSpec和子view的LayoutParams确定子view的大小  


            // 当父view的模式为EXACITY时,父view强加给子view确切的值
           //一般是父view设置为match_parent或者固定值的ViewGroup 
            switch (specMode) {  
            case MeasureSpec.EXACTLY:  
                // 当子view的LayoutParams>0,即有确切的值  
                if (childDimension >= 0) {  
                    //子view大小为子自身所赋的值,模式大小为EXACTLY  
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  

                // 当子view的LayoutParams为MATCH_PARENT时(-1)  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                    //子view大小为父view大小,模式为EXACTLY  
                    resultSize = size;  
                    resultMode = MeasureSpec.EXACTLY;  

                // 当子view的LayoutParams为WRAP_CONTENT时(-2)      
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    //子view决定自己的大小,但最大不能超过父view,模式为AT_MOST  
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                }  
                break;  

            // 当父view的模式为AT_MOST时,父view强加给子view一个最大的值。(一般是父view设置为wrap_content)  
            case MeasureSpec.AT_MOST:  
                // 道理同上  
                if (childDimension >= 0) {  
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                }  
                break;  

            // 当父view的模式为UNSPECIFIED时,父容器不对view有任何限制,要多大给多大
            // 多见于ListView、GridView  
            case MeasureSpec.UNSPECIFIED:  
                if (childDimension >= 0) {  
                    // 子view大小为子自身所赋的值  
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                    // 因为父view为UNSPECIFIED,所以MATCH_PARENT的话子类大小为0  
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    // 因为父view为UNSPECIFIED,所以WRAP_CONTENT的话子类大小为0  
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                }  
                break;  
            }  
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
        }

总结:

当父view的模式为UNSPECIFIED时(多见于ListView、GridView ),父容器不对view有任何限制,要多大给多大。此情况比较少见,这里不展开讨论,下面总结其余两种情况:

  • 1.子View指定大小值时:

Mode = MeasureSpec.EXACTLY;

Size = 指定的大小

  • 2.子View指定为MATCH_PARENT时:

Mode = 父View此时的模式;

Size = 父View的大小 – padding

  • 3.子View指定为WRAP_CONTENT时:

Mode = AT_MOST;

Size = 父View的大小 – padding, 即父View中剩余的空间

热度: 3


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK