美文网首页Android自定义View
自定义View:为什么wrap_content属性不起作用

自定义View:为什么wrap_content属性不起作用

作者: 竖起大拇指 | 来源:发表于2020-09-01 16:08 被阅读0次

    1.问题描述

    在使用自定义View时,View宽/高的wrap_content属性不起自身应用的作用,而且是起到了与match_parent相同作用?

    2.问题分析

    问题出现在View的宽/高 设置,那我们直接来看自定义View绘制中第一步对View宽/高设置的过程:measure过程中的onMeasure()方法:

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

    继续往下看getDefaultSize(),其作用是根据父View提供的宽/高测量规格计算View自身的宽/高值。源码分析如下:

     public static int getDefaultSize(int size, int measureSpec) {
              //参数说明:
             //第一个参数size:提供默认的大小
             //第二个参数:父view提供的测量规格
    
             //设置默认大小
            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;
        }
    
    

    1.getDefaultSize()的默认实现中,当View的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成父View的specSize。
    2.因为AT_MOST对应wrap_content,EXACTLY对应match_parent,所以默认情况下,wrap_content和match_parent是具有相同的效果。
    这里就解决了wrap_content起到了与match_parent相同的作用。

    那么有人会问,View的MeasureSpec是怎么赋值的?
    我们知道,View的MeasureSpec的值是根据View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的,具体计算逻辑封装在getChildMeasureSpec()里。

    我们来分析下getChildMeasureSpec的源码:

      protected void measureChild(View child, int parentWidthMeasureSpec,
                int parentHeightMeasureSpec) {
            final LayoutParams lp = child.getLayoutParams();
    
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom, lp.height);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    
    
        //根据父视图的MeasureSpec和布局参数LayoutParams,计算单个子View的MeasureSpec
        //即子View的确切大小由两方面共同决定:父view的measureSpec和子view的LayoutParams属性
    
      public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            //参数说明
            //spec:父view的详细测量值
            //padding:view当前尺寸的内边距和外边距(padding,margin)
            //childDimension:子视图的布局参数(宽/高)
    
    
            //父view的测量模式
            int specMode = MeasureSpec.getMode(spec);
    
            //父view的大小
            int specSize = MeasureSpec.getSize(spec);
    
            //通过父view计算出子view=父大小-边距
            int size = Math.max(0, specSize - padding);
            
            //子view想要的实际大小和模式(需要计算)
            int resultSize = 0;
            int resultMode = 0;
    
            switch (specMode) {
              //当父view的模式为EXACTLY时,父view强加给了子view确切的值
              //一般是父view设置为match_parent后者固定值的ViewGroup
            // Parent has imposed an exact size on us
            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) {
                    // Child wants to be our size. So be it.
                    //子view大小为父view,模式为EXACTLY
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                    
                    //当子view的LayoutParams为WRAP_CONTENT(-2)
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    //子view决定自己的大小,但是最大不能超过父View,模式为AT_MOST
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            //当父view的模式为AT_MOST 时,父view强加给了view一个最大值(一般是父View设置为了wrap_content)
            // Parent has imposed a maximum size on us
            case MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size, but our size is not fixed.
                    // Constrain child to not be bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            //当父view的模式为UNSPECIFIED时,父容器不对view有
            //任何限制,要多大给多大 多见于ListView,GridView等
            // Parent asked to see how big we want to be
            case MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                     //子view大小为子自身所赋的值
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            }
            //noinspection ResourceType
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }
    
    

    从上面可以看出,当子view的布局参数使用match_parent或wrap_content时,子view的specSize总是等于父容器当前剩余空间大小

    3.总结

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

    4.解决方案

    当自定义View的布局参数设置成wrap_content时,指定一个默认大小(宽/高)。具体是在复写onMeasure()里进行设置。

    @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);
    }
    

    这样,当你的自定义View的宽/高设置成wrap_content属性时就会生效了。

    相关文章

      网友评论

        本文标题:自定义View:为什么wrap_content属性不起作用

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