转载快速理解android View的测量onMeasure()与MeasureSpec

Posted Blue_Keroro

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了转载快速理解android View的测量onMeasure()与MeasureSpec相关的知识,希望对你有一定的参考价值。

笔者之前有一篇文章已经使用onMeasure()解决了listview与scollview的显示冲突问题,博客地址如下:

onMeasure简单方法 完美解决ListView与ScollView冲突问题!

 

在此就针对View的测量以及onMeasure()涉及的几个问题做一个详细解释:

一、MeasureSpec的概念:

MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供了打包和解包的方法。SpecMode和SpecSize也是一个int值,一组SpecMode和SpecSize可以打包为一个MeasureSpec,而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode和SpecSize。

读者只要记住以下一句话即可:

MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。

 

二、SpecMode的三种模式:

 

1. EXACTLY

当我们将控件的“layout_width”属性或者“layout_height”属性指定为具体数值时,比如“android:layout_width="200dp"”,或者指定为“match_parent”时,系统会使用这个模式。

2. AT_MOST

当控件的“layout_width”属性或者“layout_height”属性设置为“wrap_content”时,控件大小一般会随着内容的大小而变化,但是无论多大,也不能超过父控件的尺寸。

3. UNSPECIFIED

表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,一般在绘制自定义View的时候才会用到。

 

三、View的测量到底和什么有关呢?

要探其原理,首先要和大家说明一点,一个View只需要MeasureSpec确定,那么在onMeasure中就可以测量它的宽高,所以我们可以将问题直接转化成“一个View的MeasureSpec是如何确定的呢?”

 

普通的View的measure过程由VIewGroup传递而来,此处我们根据源码来做一个解释,先看一下ViewGroup中的measureChildWithMargins():

 

[java] view plain copy
 
  1. protected void measureChildWithMargins(View child,  
  2.             int parentWidthMeasureSpec, int widthUsed,  
  3.             int parentHeightMeasureSpec, int heightUsed) {  
  4.         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();  
  5.   
  6.         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
  7.                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin  
  8.                         + widthUsed, lp.width);  
  9.         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
  10.                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin  
  11.                         + heightUsed, lp.height);  
  12.   
  13.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  14.     }  
从中,我们可以看到一个view的宽高,都是通过getChildMeasureSpec()这个方法获得的,那么这里面又是怎么实现的呢?我们不妨Control+左键点进去看一下,代码如下:

 

 

[java] view plain copy
 
  1. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
  2.         int specMode = MeasureSpec.getMode(spec);  
  3.         int specSize = MeasureSpec.getSize(spec);  
  4.   
  5.         int size = Math.max(0, specSize - padding);  
  6.   
  7.         int resultSize = 0;  
  8.         int resultMode = 0;  
  9.   
  10.         switch (specMode) {  
  11.         // Parent has imposed an exact size on us  
  12.         case MeasureSpec.EXACTLY:  
  13.             if (childDimension >= 0) {  
  14.                 resultSize = childDimension;  
  15.                 resultMode = MeasureSpec.EXACTLY;  
  16.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  17.                 // Child wants to be our size. So be it.  
  18.                 resultSize = size;  
  19.                 resultMode = MeasureSpec.EXACTLY;  
  20.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  21.                 // Child wants to determine its own size. It can‘t be  
  22.                 // bigger than us.  
  23.                 resultSize = size;  
  24.                 resultMode = MeasureSpec.AT_MOST;  
  25.             }  
  26.             break;  
  27.   
  28.         // Parent has imposed a maximum size on us  
  29.         case MeasureSpec.AT_MOST:  
  30.             if (childDimension >= 0) {  
  31.                 // Child wants a specific size... so be it  
  32.                 resultSize = childDimension;  
  33.                 resultMode = MeasureSpec.EXACTLY;  
  34.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  35.                 // Child wants to be our size, but our size is not fixed.  
  36.                 // Constrain child to not be bigger than us.  
  37.                 resultSize = size;  
  38.                 resultMode = MeasureSpec.AT_MOST;  
  39.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  40.                 // Child wants to determine its own size. It can‘t be  
  41.                 // bigger than us.  
  42.                 resultSize = size;  
  43.                 resultMode = MeasureSpec.AT_MOST;  
  44.             }  
  45.             break;  
  46.   
  47.         // Parent asked to see how big we want to be  
  48.         case MeasureSpec.UNSPECIFIED:  
  49.             if (childDimension >= 0) {  
  50.                 // Child wants a specific size... let him have it  
  51.                 resultSize = childDimension;  
  52.                 resultMode = MeasureSpec.EXACTLY;  
  53.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  54.                 // Child wants to be our size... find out how big it should  
  55.                 // be  
  56.                 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;  
  57.                 resultMode = MeasureSpec.UNSPECIFIED;  
  58.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  59.                 // Child wants to determine its own size.... find out how  
  60.                 // big it should be  
  61.                 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;  
  62.                 resultMode = MeasureSpec.UNSPECIFIED;  
  63.             }  
  64.             break;  
  65.         }  
  66.         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
  67.     }  

 

代码比较长,读者可以比较焦急——不用害怕,我们不需要完全理解它的原理,我们只需要知道View的测量是如何实现的就行了。

看到源码方法中的三个参数,并且比较measureChildWithMargins()方法中传递给getChildMeasureSpec()三个值,我们很快就可以理解,一个View的测量过程是由父布局的MeasureSpec和该View的LayoutParams决定的。

 

读者可以看measureChildWithMargins()中如下代码:

 

[java] view plain copy
 
  1. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
  2.                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin  
  3.                         + widthUsed, lp.width);  

 

 

要测量子部局的宽度的MeasureSpec,需要传入3个参数:

第一个参数:父布局的宽度的MeasureSpec

第二个参数:子部局的padding值,子部局的LayoutParams的Margin值

第三个参数:子部局的LayoutParams的宽度

 

 

 

如果对View的测量过程由更深入的求知欲的,推荐读者可以自己看一下源码。

版权声明:本文为博主许佳佳原创文章,转载请务必注明出处。 https://blog.csdn.net/Double2hao/article/details/51553703

以上是关于转载快速理解android View的测量onMeasure()与MeasureSpec的主要内容,如果未能解决你的问题,请参考以下文章

android View的测量和绘制

Android自定义View(三深入解析控件测量onMeasure)

深入理解View知识系列四-View的测量规则以及三大方法流程

深入理解View知识系列四-View的测量规则以及三大方法流程

分析Android中View的工作流程

Android自定义view之view显示流程