美文网首页
View的测量过程

View的测量过程

作者: 三十二蝉 | 来源:发表于2018-03-14 05:47 被阅读17次

    onMeasure方法:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    //参数说明:View的宽/高  测量规格
    
    //setMeasuredDimension()  用于获得View宽/高的测量值
    //这两个参数是通过getDefaultSize()获得的
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    }
    

    getDefaultSize方法:

    public static int getDefaultSize(int size, int measureSpec) {  
    
    //参数说明:
    // 第一个参数size:提供的默认大小
    // 第二个参数:宽/高的测量规格(含模式 & 测量大小)
    
        //设置默认大小
        int result = size; 
    
        //获取宽/高测量规格的模式 & 测量大小
        int specMode = MeasureSpec.getMode(measureSpec);  
        int specSize = MeasureSpec.getSize(measureSpec);  
    
        switch (specMode) {  
            // 模式为UNSPECIFIED时,使用提供的默认大小
            // 即第一个参数:size 
            case MeasureSpec.UNSPECIFIED:  
                result = size;  
                break;  
            // 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值
            // 即measureSpec中的specSize
            case MeasureSpec.AT_MOST:  
            case MeasureSpec.EXACTLY:  
                result = specSize;  
                break;  
        }  
    
     //返回View的宽/高值
        return result;  
    }
    

    在getDefaultSize()的默认实现中,当View的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize。

    子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来,具体计算逻辑封装在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);  
    
    子布局参数/父布局MeasureSpec EXACTLY AT_MOST UNSPECIFIED
    具体数值 EXACTLY + childSize EXACTLY + childSize EXACTLY + childSize
    match_parent EXACTLY + parentSize(父控件剩余空间) AT_MOST + parentSize (大小不超过父控件的剩余空间) UNSPECIFIED + 0
    wrap_content AT_MOST + parentSize(大小不超过父容器的剩余空间) AT_MOST + parentSize(大小不超过父容器的剩余空间) UNSPECIFIED + 0

    issue:为什么自定义View的wrap_content不生效?

    • 在onMeasure()中的getDefaultSize()的默认实现中,当View的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成View MeasureSpec的specSize。
    • 因为AT_MOST对应wrap_content;EXACTLY对应match_parent,所以,默认情况下,wrap_content和match_parent是具有相同的效果的。
    • 因为在计算子View MeasureSpec的getChildMeasureSpec()中,子View MeasureSpec在属性被设置为wrap_content或match_parent情况下,子View MeasureSpec的specSize被设置成parenSize = 父容器当前剩余空间大小
      所以,wrap_content起到了和match_parent相同的作用:等于父容器当前剩余空间大小

    solve

    当自定义View的布局参数设置成wrap_content时,指定一个默认大小(宽 / 高)

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            // 获取宽-测量规则的模式和大小
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
            // 获取高-测量规则的模式和大小
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            // 设置wrap_content的默认宽 / 高值
            // 默认宽/高的设定并无固定依据,根据需要灵活设置
            // 类似TextView,ImageView等针对wrap_content均在onMeasure()对设置默认宽 / 高值有特殊处理,具体读者可以自行查看
            int mWidth = 400;
            int mHeight = 400;
    
          // 当布局参数设置为wrap_content时,设置默认值
            if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                setMeasuredDimension(mWidth, mHeight);
            // 宽 / 高任意一个布局参数为= wrap_content时,都设置默认值
            } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                setMeasuredDimension(mWidth, heightSize);
            } else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                setMeasuredDimension(widthSize, mHeight);
            }
    

    另一种写法:

    // 当模式是AT_MOST(即wrap_content)时设置默认值
            if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(mWidth, mHeight);
            // 宽 / 高任意一个模式为AT_MOST(即wrap_content)时,都设置默认值
            } else if (widthMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(mWidth, heightSize);
            } else if (heightMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(widthSize, mHeight);
            }
    

    上述的解决方案是:通过判断测量模式是否ATMOST从而来判断View的参数是否是wrap_content
    可是,View的AT_MOST模式对应的不只是wrap_content,也有可能是match_parent ;即当父View是AT_MOST、View的属性设置为match_parent时。这种情况虽然是match_parent,但是还是会按照wrap_content的方式布局,所以结果也是正确的。

    相关文章

      网友评论

          本文标题:View的测量过程

          本文链接:https://www.haomeiwen.com/subject/yckvfftx.html