measure方法,主要是用于测量android中view的大小,为后面的layout做好准备,这里我们主要来看measure的流程。
查看view中的方法,
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
这个方法是测量方法,但是这里这个方法是final的,也就是说无法重写,其实这里面最终是调用onMeasure来完成测量。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
android的视图测量就是从上到下遍历的测量,他的流程如下:
比较简单,值得注意的是measure方法中有一个widthMeasureSpec值,这个值是一个int,它的高两位代表了specMode,低30位代表了specsize,可以使用MeasureSpec来打包和解包这个值,从而获得mode和size。
先来看看MeasureSpec的mode:
UNSPECIFIED//不做限制
EXACTLY//指定精确值,在match_parent和具体数值时,适用与这种场景
AT_MOST//指定最大值,往往对应于layoutParams中的wrap_content
一个普通view的mode如何来确立呢,我们查看android中viewGroup的源码中的getChildMeasureSpec方法,如下:
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) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
这里的代码其实比较简单,总的来说,从参数来看,需要的参数是当前viewGroup的MeasureSpec以及padding,还有子view的大小,在里面判断的时候,还会结合子view的layoutParams。这里可以把上面的代码总结成一个表格:
可以看到,parent和子view的关系。
在自定义控件的时候,如果不重写onMeasure方法,往往wrap_content配置不起作用,这是为什么呢。我们先来看看View的onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
这里面调用了setMeasuredDimension方法来设置最终的大小,所以这里面决定大小的实际上是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;
}
这里我们先不管参数size,在AT_MOST和EXACTLY这两种模式下用不到,我们结合前面讲解的getChildMeasureSpec方法以及表格可以知道,当前view的MeasureSpec是根据parent和当前view的layoutParams决定的,例如wrap_content,这个在上面的表格中有所体现,可以看到,最终wrap_content和match_parent和parent的mode结合后,就会产生AT_MOST和EXACTLY这两种模式,且size都是parent的size,而上面的方法中,这两种模式的处理方式都是直接返回specSize,由于size是parent的size,也就导致了其实wrap_content和match_parent一致。
所以我们需要重写onMeasure方法来实现wrap_content
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
//宽高都是wrap_content
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
//宽是wrap_content
setMeasuredDimension(mWidth, heightMeasureSpec);
} else if (heightMeasureSpec == MeasureSpec.AT_MOST) {
//高是wrap_content
setMeasuredDimension(widthMeasureSpec, mHeight);
}
}
这里的mWidth和mHeight是自己根据情况自定的,以便实现wrap_content效果。
要获取view的宽高,必须等待view测量完毕后才能得到正确的大小,那么如何保证获取的时候已经测量完毕,这里一共有四种方法
这个方法activity和view都有,在焦点发生变化的时候,就会回调这个方法。
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasFocus()) {
//获取大小
}
}
我们知道,android中view的事件,包括touch,测量等等,都是通过发送消息到ui线程来执行的,那么使用这个方法,可以将我们获取大小的方法加入到队列末端。从而保证在测量完成后执行。
post(new Runnable() {
@Override
public void run() {
//获取大小
}
})
这个类可以是一个view树的观察者,当它内部的view发生变化的时候就会回调其中响应的方法,这里使用OnGlobalLayoutListener,当view的layout状态发生变化的时候,就会回调。由于layout是发生在onmeasure之后的,所以保证了测量的完成。由于这个方法可能多次回调,所以在回调中,需要注销监听。
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
//获取大小
}
});
这种方法是直接调用view的measure方法来手动测量,但是这里要根据不同的layoutParams来做不同的方法。
match_parent
这种情况没办法测量,因为这里并不知道parent的大小。
具体数值
指定了具体数值,那么根据前面的表格,可以看到,mode就是EXACTLY,所以可以使用如下代码:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
这里的width和height就是指定的具体宽高。
wrap_content
同样的,根据前面的表格,这里的模式是AT_MOST,所以使用如下代码:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1, MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1, MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
前面也讲到了,这里的widthMeasureSpec高两位是mode,低30是大小。所以必须左移30后减1,从而得到最大的值。
这篇文章介绍了measure的流程,以及应用,就到这里。