今天探讨下view的绘制流程,我们知道系统一个view控件想在屏幕上显示,一般要经过三个过程,测量,计算大小,绘制,也就说想让控件显示在屏幕上首先要做的是测量也就是onMeasure(),现在我们自定义一个View对象,然后跟到源码里去看看,
MyView.java
public class MyView extends View {
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
Log.e("MyView","mode="+mode+"size="+size);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
View源码中有一个measure方法,也就是测量,源码如下:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~MEASURED_DIMENSION_SET;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
}
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
不过发现measure这个方法被final修饰了,表示子类不能重写,但是我们仔细看源码发现其实真正测量方法是在onMeasure()方法中,那我们可以重写这个方法来实现我们要实现的效果,这就是为什么在MyView中重写了onMeasure()方法,
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
上面是onMeasure()方法,最终还是通过setMeasuredDimension()方法实现其作用,但是分析到这里好像和我们想要知道的结果没关系,对吧,别急,现在看我们MyView中重写的onMeasure()方法代码,方法中有2个形参,int widthMeasureSpec, int heightMeasureSpec,这2个参数是父控件传递过来的,现在看一段代码
int mode = MeasureSpec.getMode(heightMeasureSpec);
我们进入源码看下这个方法怎么实现的,
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
我们发现其实宽度和高度并不是单一的由size决定,我们知道size就是控件的宽或者高,那么mode是什么呢?我们找找在哪定义了这个变量,发现并没有在源码中找到,因为这时我们传递进来的,那就没办法知道这个mode是什么了么?办法还是有的,可以查看makeMeasureSpec这个方法的解析,把鼠标放在这个方法上,按f2,我截图看看
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格
我们看到mode有三个值,定义在MeasureSpec类中,现在解释下这三个变量,
EXACTLY:
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小
AT_MOST:
表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小
UNSPECIFIED:
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到
那到源码中找到MeasureSpec类,看看它三个变量,因为一般这样的值都是固定的,
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* Creates a measure specification based on the supplied size and mode.
*
* The mode must always be one of the following:
* <ul>
* <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
* <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
* <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
* </ul>
*
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
/**
* Returns a String representation of the specified measure
* specification.
*
* @param measureSpec the measure specification to convert to a String
* @return a String with the following format: "MeasureSpec: MODE SIZE"
*/
public static String toString(int measureSpec) {
int mode = getMode(measureSpec);
int size = getSize(measureSpec);
StringBuilder sb = new StringBuilder("MeasureSpec: ");
if (mode == UNSPECIFIED)
sb.append("UNSPECIFIED ");
else if (mode == EXACTLY)
sb.append("EXACTLY ");
else if (mode == AT_MOST)
sb.append("AT_MOST ");
else
sb.append(mode).append(" ");
sb.append(size);
return sb.toString();
}
}
这个类是View类中的一个内部类,发现其对应得值为0,1,2
那现在打印出MyView中onMeasure()中的mode是多少,因为源码中值都是向左移动了30,因此在获取mode后要向右移动30,这样的
int mode = MeasureSpec.getMode(heightMeasureSpec)>>>30;
这个mode值才和MeasureSpec类中定义的三个静态变量值有一个是相同的,也就是mode在0,1,2三个中其中一个了,
结果是1,那就明白了
view的测量就分析到此,