自定义View之View的测量
1.view的测量时机
当view处于某个父布局里面,onMeasure()方法被父view调用,开始测量大小,可见view的大小是由其父布局和自身大小共同决定的。
2.view的测量流程
系统的测量方法
protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
传入该view想要的大小,以及对应的测量模式MeasureSpec(父View传入的),然后调用setMeasuredDimension()方法就实现了view的测量。
3.MeasureSpec类
MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小。
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
- EXACTLY:精确值模式(1073741824)
当我们将空间的layout_width或者layout_height属性指定为具体值时,或者指定为match_parent属性时,系统使用的是EXACTLY。
- AT_MOST:最大值模式(-2147483648)
当空间的layout_width属性或者layout_height属性为wrap_content时,控件大小一般随着空间的子控件或者内容的变化而变化,此时,控件的尺寸只要不超过父控件允许的最大尺寸即可。
- UNSPECIFIED :无规则模式 (0)
不指定其测量大小,通常情况下在绘制自定义View时才会使用它。
image从最顶层的DecorVie测量模式说起
/**
* Figures out the measure spec for the root view
* in a window based on it's layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the window.
*
* @return The measure spec to use to measure the root view.
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
//匹配父容器时,测量模式为MeasureSpec.EXACTLY,
//测量大小直接为屏幕的大小,也就是充满真个屏幕
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,
MeasureSpec.EXACTLY);
break;
//包裹内容时,测量模式为MeasureSpec.AT_MOST,
//测量大小直接为屏幕大小,也就是充满真个屏幕
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,
MeasureSpec.AT_MOST);
break;
//其他情况时,测量模式为MeasureSpec.EXACTLY,
//测量大小为DecorView顶层视图布局设置的大小。
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,
MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
分析:该方法主要作用是在整个窗口的基础上计算出root view(顶层视图DecorView)的测量规格,该方法的两个参数分别表示:
windowSize:当前手机窗口的有效宽和高,一般都是除了通知栏的屏幕宽和高
rootDimension 根布局DecorView请求的宽和高,在布局中是MATCH_PARENT。
可知,我们的DecorView根布局宽和高都是MATCH_PARENT,因此DecorView根布局的测量模式就是MeasureSpec.EXACTLY,测量大小一般都是整个屏幕大小,所以一般我们的Activity窗口都是全屏的。
从最顶层Decoriew发起测量,就是view树自顶而下进行遍历,不断调用子view的onMeasure方法测量其自身大小。系统默认测量方法如下:
protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
/**
* 为宽度获取一个建议最小值
*/
protected int getSuggestedMinimumWidth () {
return (mBackground == null) ? mMinWidth : max(mMinWidth , mBackground.getMinimumWidth());
}
/**
* 获取默认的宽高值
*/
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;
}
由此可知,自定义view直接继承veiw,布局参数:wrap_content,结合传父view的MeasureSpec,此时子view的MeasureSpec判定为AT_MOST,并且specSize等于父view的宽高。默认情况下WRAP_CONTENT 的处理也将填充整个父控件。
4.自定义veiw直接继承view测量要点
注意要点:
- AT_MOST模式下必须重写测量方法onMeasure()方法
- 自定义图形文字,除了计算自身图形或者文本的宽高,支持padding,还要计算padding
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int defaultwith = (int) (getPaddingLeft() + getPaddingRight());
int defaultHeight = (int) (getPaddingTop() + getPaddingBottom());
setMeasuredDimension(getWidthSize(defaultwith, widthMeasureSpec), getHeightSize(defaultHeight, heightMeasureSpec));
}
public static int getWidthSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
//LogUtil.d("width specMode =" + specMode + ",specSize" + result);
break;
case MeasureSpec.AT_MOST:
result = Math.min(result, specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
// LogUtil.d("width specMode =" + specMode + ",specSize" + result);
break;
}
return result;
}
public static int getHeightSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
// LogUtil.d("Height specMode =" + specMode + ",specSize" + result);
break;
case MeasureSpec.AT_MOST:
result = Math.min(result, specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
// LogUtil.d("Height specMode =" + specMode + ",specSize" + result);
break;
}
return result;
}