美文网首页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