美文网首页
自定义View的wrap_content属性失效

自定义View的wrap_content属性失效

作者: 同学别闹 | 来源:发表于2018-06-05 00:25 被阅读0次

      前段时间做项目绘制一个View,想要控件包裹内容后控件的宽高是绘制区域的大小,但是绘制好之后,放入到不居中,使用wrap_content的宽高,控件默认显示的大小是match_parent。

    先分析一下为什么会出现问题

    • 首先看一下onMeasure在源码中是如何实现的:
    /**
       * 首先调用setMeasuredDimension来存储测量宽度和测量高度
       *  在调用getDefaultSize方法获取对应的宽高,所以关键分析一下* getDefaultSize是如何实现的
       */
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          //存储测量宽度和测量高度
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    
    /**
       * @param size 提供的默认大小
       * @param measureSpec 测量规格(包含测量模式和测量大小)
       * 32位的int值,高2位位测量模式,地30位为测量大小
       * ScrollerView雨ListView或者GridView冲突的时候
       * 只需要在onMeasure的super()方法之前指定大小
       * Integer.MAX_VALUE >> 2 (即int值左移动2位得到最大的测量值,指定模式为AT_MOST)
       * int height = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
       * View.MeasureSpec.AT_MOST);
       * @return 测量后的值
       */
      public static int getDefaultSize(int size, int measureSpec) {
        //传递过来的默认大小值
        int result = size;
        //获取测量模式
        int specMode = View.MeasureSpec.getMode(measureSpec);
        //获取测量大小
        int specSize = View.MeasureSpec.getSize(measureSpec);
        switch (specMode) {
          //使用默认大小
          case View.MeasureSpec.UNSPECIFIED:
            result = size;
            break;
            //使用测量后的大小
          case View.MeasureSpec.AT_MOST:
          case View.MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        //返回View的宽或高
        return result;
      }
    
    

      分析源码后发现AT_MOST 和 EXACTLY模式的时候都使用测量后的大小,而AT_MOST对应的是wrap_content的宽高,EXACTLY对应的是match_parent的宽高,所以默认情况下AT_MOST和EXACTLY使用的都是match_parent的宽高,所以会出现自定义View使用wrap_content的时候布局会撑满全屏。

    分析了问题后就要进行对症下药

      首先考虑平时使用的TextView,ImageView等控件的wrap_content属性都是有效的,源码中肯定对onMeasure做了处理。看先下TextView中对onMeasure是如何处理的。

     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //获取宽的测量模式
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            //获取的高测量模式
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
           //获取的测量额宽
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
           //获取的测量额高
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            //指定宽高值 
            int width;
            int height;
            BoringLayout.Metrics boring = UNKNOWN_BORING;
            BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
            // 判断文字绘制的方向
            if (mTextDir == null) {
                mTextDir = getTextDirectionHeuristic();
            }
            int des = -1;
            boolean fromexisting = false;
            
            if (widthMode == MeasureSpec.EXACTLY) {
              //使用默认的测量值
                width = widthSize;
            } else {
            //解决AT_MOST的时候wrap_content失效,对内容区域进行宽度测量给width赋值
            if (mLayout != null && mEllipsize == null) {
                    des = desired(mLayout);
                }
               //...此处省略代码
                if (boring == null || boring == UNKNOWN_BORING) {
                    if (des < 0) {
                        //更具内容获取到内容显示需要的宽度
                        des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, 0,
                                mTransformed.length(), mTextPaint, mTextDir));
                    }
                  //更具内容区域进行width赋值
                    width = des;
                } else {
                  //...此处省略代码 与上类同进行更具内容进行测量需要的宽度对width进行赋值
            }
    
               //...以上省略代码
    
       
            if (heightMode == MeasureSpec.EXACTLY) {
                height = heightSize;
                mDesiredHeightAtMeasure = -1;
            } else {
                //获取到内容显示区域需要的高度,对height进行赋值
                int desired = getDesiredHeight();
                height = desired;
                mDesiredHeightAtMeasure = desired;
                if (heightMode == MeasureSpec.AT_MOST) {
                    height = Math.min(desired, heightSize);
                }
            }
        }
              //...以上省略代码
    
            // 设置测量好的宽高
           setMeasuredDimension(width, height);
    }
    

      简单查看TextView的源码发现,在onMeasure方法中,单独对EXACTLY测量模式以外的的模式进行内容区域所占宽高进行测量,然后调用setMeasuredDimension对宽高重新赋值。

    解决方案

     @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取显示内容宽高
        int desiredWidth = this.desiredWidth;
        int desiredHeight = this.desiredHeight;
        
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
          
        int width;
        int height;
        //根据测量模式重新计算宽
        if (widthMode == MeasureSpec.EXACTLY) {
          width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
          width = Math.min(desiredWidth, widthSize);
        } else {
          width = desiredWidth;
        }
        //根据测量模式重新计算高
        if (heightMode == MeasureSpec.EXACTLY) {
          height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
          height = Math.min(desiredHeight, heightSize);
        } else {
          height = desiredHeight;
        }
        //对宽高重新赋值
        setMeasuredDimension(width, height);
      }
    

    相关文章

      网友评论

          本文标题:自定义View的wrap_content属性失效

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