自定义View的wrap_content属性失效

2018-06-05  本文已影响0人  同学别闹

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

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

/**
   * 首先调用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);
  }
上一篇下一篇

猜你喜欢

热点阅读