美文网首页Android开发Android知识
onMeasure()源码详细分析

onMeasure()源码详细分析

作者: 岛在深海处 | 来源:发表于2016-08-23 12:53 被阅读0次

昨晚写的第三章 控件架构与自定义控件详解读书笔记中已经对控件树已经View的测量有了一定了解今天结合ViewGroup的测量来具体分析下onMeasure()的源码。

1.ViewGroup测量子View流程

measureChildWithMargins()/measureChild()->child.measure()->child.onMeasure-> setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); );

  • 其中measureChildWithMargins()中调用getChildMeasureSpec()通过ViewGroup的MeasureSpec来确定子View的MeasureSpec。
  • measureChild( )计算父View所占空间为mPaddingLeft + mPaddingRight,即父容器左右两侧的padding值之和
  • measureChildWithMargins( )计算父View所占空间为mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed。此处,除了父容器左右两侧的padding值之和还包括了子View左右的margin值之和( lp.leftMargin + lp.rightMargin),因为这两部分也是不能用来摆放子View的应算作父View已经占用的空间。这点从方法名measureChildWithMargins也可以看出来它是考虑了子View的margin所占空间的。
  • 最后看下getDefaultSize()源码:
    public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
    result = size;
    break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
    result = specSize;
    break;
    }
    return result;
    }

除去第一种情况不考虑以外,可知: 在measure阶段View的宽和高由其measureSpec中的specSize决定!!

  • 终于搞懂了一个问题,如果子View在XML布局文件中对于大小的设置采用wrap_content,那么不管父View的specMode是MeasureSpec.AT_MOST还是MeasureSpec.EXACTLY对于子View而言系统给它设置的specMode都是MeasureSpec.AT_MOST,并且其大小都是parentLeftSize即父View目前剩余的可用空间。这时wrap_content就失去了原本的意义,变成了match_parent一样了。

  • 我们再回过头来看看getChildMeasureSpec()方法:
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = View.MeasureSpec.getMode(spec);
    int specSize = View.MeasureSpec.getSize(spec);

      int size = Math.max(0, specSize - padding);
    
      int resultSize = 0;
      int resultMode = 0;
    
      switch (specMode) {
          case View.MeasureSpec.EXACTLY:
              if (childDimension >= 0) {
                  resultSize = childDimension;
                  resultMode = View.MeasureSpec.EXACTLY;
              } else if (childDimension == LayoutParams.MATCH_PARENT) {
                  resultSize = size;
                  resultMode = View.MeasureSpec.EXACTLY;
              } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                  resultSize = size;
                  resultMode = View.MeasureSpec.AT_MOST;
              }
              break;
    
          case View.MeasureSpec.AT_MOST:
              if (childDimension >= 0) {
                  resultSize = childDimension;
                  resultMode = View.MeasureSpec.EXACTLY;
              } else if (childDimension == LayoutParams.MATCH_PARENT) {
                  resultSize = size;
                  resultMode = View.MeasureSpec.AT_MOST;
              } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                  resultSize = size;
                  resultMode = View.MeasureSpec.AT_MOST;
              }
              break;
    
          case View.MeasureSpec.UNSPECIFIED:
              if (childDimension >= 0) {
                  resultSize = childDimension;
                  resultMode = View.MeasureSpec.EXACTLY;
              } else if (childDimension == LayoutParams.MATCH_PARENT) {
                  resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                  resultMode = View.MeasureSpec.UNSPECIFIED;
              } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                  resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                  resultMode = View.MeasureSpec.UNSPECIFIED;
              }
              break;
      }
      return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    

    }
    最终解释可借助下图:


    既然在ViewGroup中wrap_content失去了原本的意义,那图中的绿色标记部分,该如何处理呢?
  • 第一种情况: 如果在xml布局中View的宽和高均用wrap_content.那么需要设置 View的宽和高为mWidth和mHeight。

  • 第二种情况: 如果在xml布局中View的宽或高其中一个为wrap_content,那么就将该值设置为默认的宽或高,另外的一个值采用系统测量的specSize即可。
    具体实现可参考:
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec , heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
    int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec);
    if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
    setMeasuredDimension(mWidth, mHeight);
    }else if(widthSpecMode==MeasureSpec.AT_MOST){
    setMeasuredDimension(mWidth, heightSpceSize);
    }else if(heightSpecMode==MeasureSpec.AT_MOST){
    setMeasuredDimension(widthSpceSize, mHeight);
    }

    }
    

参考:自定义View系列教程02--onMeasure源码详尽分析

相关文章

网友评论

    本文标题:onMeasure()源码详细分析

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