MeasureSpec的理解和源码的解析

MeasureSpec是Android视图测量过程中的核心概念,用于确定视图的尺寸。它的本质是一个32位整数,其中高两位表示测量模式(SpecMode),低30位表示测量大小(SpecSize)。MeasureSpec提供了三种主要的测量模式:MeasureSpec.EXACTLY、MeasureSpec.AT_MOST和MeasureSpec.UNSPECIFIED。

1. MeasureSpec.EXACTLY:这种模式表示父容器已经决定了子视图的确切尺寸。当子视图的LayoutParams设置为具体数值(如像素值)或`match_parent`时,会触发此模式。在这种情况下,无论父容器的MeasureSpec如何,对于设置了具体数值的子视图,其specMode始终为MeasureSpec.EXACTLY,specSize则等于视图设定的大小。如果LayoutParams为`match_parent`,specSize将等于父容器的剩余空间大小。

2. MeasureSpec.AT_MOST:AT_MOST模式意味着子视图可以尽可能大,但不能超过父容器给出的最大限制。例如,当子视图的LayoutParams设置为`wrap_content`,并且父容器的MeasureSpec是MeasureSpec.AT_MOST时,子视图将根据自己的内容来决定大小,但不能超过父容器给出的最大边界。在这种模式下,specMode为MeasureSpec.AT_MOST,specSize为父容器提供的最大尺寸。

3. MeasureSpec.UNSPECIFIED:这种模式表示子视图的大小可以是任意值,只要不超过父容器的剩余空间即可。在这种模式下,specMode为MeasureSpec.UNSPECIFIED,specSize为父容器的剩余空间大小。

实例详解:

```java

package cc.ww;

import android.view.View;

import android.view.View.MeasureSpec;

import android.view.ViewGroup.LayoutParams;

import android.view.ViewGroup.MarginLayoutParams;

import android.widget.LinearLayout;

/**

* @author http://blog.csdn.n

*/

public class MeasureSpecDemo extends LinearLayout {

public MeasureSpecDemo(Context context) {

super(context);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

// 获取父布局的MeasureSpec

int parentWidthMeasureSpec = MeasureSpec.getSize(widthMeasureSpec);

int parentHeightMeasureSpec = MeasureSpec.getSize(heightMeasureSpec);

// 根据不同的测量模式进行计算

if (/* 使用具体的宽度 */) {

setMeasuredDimension(/* 具体宽度 */, /* 具体高度 */);

} else if (/* 使用最大宽度 */) {

setMeasuredDimension(Math.min(parentWidthMeasureSpec, /* 最大宽度 */), getPreferredHeight());

} else if (/* 使用最大高度 */) {

setMeasuredDimension(getPreferredWidth(), Math.min(parentHeightMeasureSpec, /* 最大高度 */));

} else {

setMeasuredDimension(getPreferredWidth(), getPreferredHeight());

}

}

}

```

UNSPECIFIED模式是最宽松的,表示父容器对子视图的大小没有任何要求,子视图可以根据自己的需求自由决定大小。在实际开发中,这种模式并不常见,但在某些情况下,比如在视图首次加载或视图树的初始化阶段可能会遇到。

当父容器需要测量其子视图时,首先会进行自身测量,然后根据自身的MeasureSpec和LayoutParams来为子视图提供测量规格。子视图接收到MeasureSpec后,会调用`onMeasure()`方法,根据specMode和specSize来计算自己的理想尺寸。这个过程通常涉及到递归的测量,因为子视图可能还有自己的子视图需要测量。

源码解析:

MeasureSpec类提供了一些静态方法来创建和解析MeasureSpec对象。例如,可以通过`MeasureSpec.makeMeasureSpec(int size, int mode)`方法创建一个MeasureSpec对象,其中size参数是测量大小,mode参数是测量模式。同时,`MeasureSpec.getMode(int measureSpec)`和`MeasureSpec.getSize(int measureSpec)`分别用于获取MeasureSpec中的模式和大小。

在自定义视图时,了解和使用MeasureSpec非常重要,因为它决定了视图的布局和尺寸计算。开发者需要根据MeasureSpec提供的信息来调整视图的大小,以确保视图在不同屏幕尺寸和布局条件下正确显示。通过深入理解MeasureSpec的工作原理,可以更好地优化应用的性能和用户体验。以下是一个简单的示例代码:

```java

public class CustomView extends View {

public CustomView(Context context) {

super(context);

}

public CustomView(Context context, AttributeSet attrs) {

super(context, attrs);

}

public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int parentWidth = MeasureSpec.getSize(widthMeasureSpec);

int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

int childWidth;

int childHeight;

// 根据父容器和子视图的需求计算子视图的理想尺寸

if (/* 需要填充整个父容器 */) {

childWidth = parentWidth;

childHeight = parentHeight;

} else if (/* 需要填充整个父容器的高度 */) {

childWidth = parentWidth;

childHeight = parentHeight * /* 高度比例 */;

} else if (/* 需要填充整个父容器的宽度 */) {

childWidth = parentWidth * /* 宽度比例 */;

childHeight = parentHeight;

} else {

// 其他情况,子视图按照其需求自由定义尺寸

int childMode = MeasureSpec.getMode(widthMeasureSpec);

int childSize = MeasureSpec.getSize(widthMeasureSpec);

childWidth = resolveSize(childMode, childSize);

int childMode2 = MeasureSpec.getMode(heightMeasureSpec);

int childSize2 = MeasureSpec.getSize(heightMeasureSpec);

childHeight = resolveSize(childMode2, childSize2);

}

// 将子视图的尺寸传递给布局管理器进行定位和布局

setMeasuredDimension(childWidth, childHeight);

}

}

```