笔者以前有一篇文章已经使用onMeasure()解决了listview与scollview的显示冲突问题,博客地址以下:html
onMeasure简单方法 完美解决ListView与ScollView冲突问题!java
在此就针对View的测量以及onMeasure()涉及的几个问题作一个详细解释:android
1、MeasureSpec的概念:布局
MeasureSpec经过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操做,其提供了打包和解包的方法。SpecMode和SpecSize也是一个int值,一组SpecMode和SpecSize能够打包为一个MeasureSpec,而一个MeasureSpec能够经过解包的形式来得出其原始的SpecMode和SpecSize。post
读者只要记住如下一句话便可:spa
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。.net
2、SpecMode的三种模式:htm
1. EXACTLY对象
当咱们将控件的“layout_width”属性或者“layout_height”属性指定为具体数值时,好比“android:layout_width="200dp"”,或者指定为“match_parent”时,系统会使用这个模式。blog
2. AT_MOST
当控件的“layout_width”属性或者“layout_height”属性设置为“wrap_content”时,控件大小通常会随着内容的大小而变化,可是不管多大,也不能超过父控件的尺寸。
3. UNSPECIFIED
表示开发人员能够将视图按照本身的意愿设置成任意的大小,没有任何限制。这种状况比较少见,通常在绘制自定义View的时候才会用到。
3、View的测量到底和什么有关呢?
要探其原理,首先要和你们说明一点,一个View只须要MeasureSpec肯定,那么在onMeasure中就能够测量它的宽高,因此咱们能够将问题直接转化成“一个View的MeasureSpec是如何肯定的呢?”
普通的View的measure过程由VIewGroup传递而来,此处咱们根据源码来作一个解释,先看一下ViewGroup中的measureChildWithMargins():
- protected void measureChildWithMargins(View child,
- int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed) {
- final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
-
- final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
- mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
- + widthUsed, lp.width);
- final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
- mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
- + heightUsed, lp.height);
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
从中,咱们能够看到一个view的宽高,都是经过getChildMeasureSpec()这个方法得到的,那么这里面又是怎么实现的呢?咱们不妨Control+左键点进去看一下,代码以下:
- public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
- int specMode = MeasureSpec.getMode(spec);
- int specSize = MeasureSpec.getSize(spec);
-
- int size = Math.max(0, specSize - padding);
-
- int resultSize = 0;
- int resultMode = 0;
-
- switch (specMode) {
-
- case MeasureSpec.EXACTLY:
- if (childDimension >= 0) {
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.MATCH_PARENT) {
-
- resultSize = size;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- }
- break;
-
-
- case MeasureSpec.AT_MOST:
- if (childDimension >= 0) {
-
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.MATCH_PARENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- } else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- }
- break;
-
-
- case MeasureSpec.UNSPECIFIED:
- if (childDimension >= 0) {
-
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.MATCH_PARENT) {
-
-
- resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
- resultMode = MeasureSpec.UNSPECIFIED;
- } else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
- resultMode = MeasureSpec.UNSPECIFIED;
- }
- break;
- }
- return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
- }
代码比较长,读者能够比较焦急——不用惧怕,咱们不须要彻底理解它的原理,咱们只须要知道View的测量是如何实现的就好了。
看到源码方法中的三个参数,而且比较measureChildWithMargins()方法中传递给getChildMeasureSpec()三个值,咱们很快就能够理解,一个View的测量过程是由父布局的MeasureSpec和该View的LayoutParams决定的。
读者能够看measureChildWithMargins()中以下代码:
- final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
- mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
- + widthUsed, lp.width);
要测量子部局的宽度的MeasureSpec,须要传入3个参数:
第一个参数:父布局的宽度的MeasureSpec
第二个参数:子部局的padding值,子部局的LayoutParams的Margin值
第三个参数:子部局的LayoutParams的宽度
若是对View的测量过程由更深刻的求知欲的,推荐读者能够本身看一下源码。