View的measure
对于View的绘制过程,是由ViewRootImpl.performTraversals()执行的,在performTraversals方法里,分别执行了performMeasure、performLayout、performDraw三个方法,分别是View的测量,View的位置,View的绘制,如下:
private void performTraversals(){
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
而performMeasure方法里执行了调用view的measure方法,如下:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//看这里
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
而View的measure方法会调用onMeasure方法,如下:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
一般我们自定义view的时候都会重写onMeasure方法,而在Activity的decorView默认是FrameLayout布局,所以,重写的是FrameLayout的onMeasure方法。
这里要讲述的就是onMeasure方法
说到View的onMeasure方法,就要说一下MeasureSpec这个类,这个类我对它的理解就是View测量的规格类,指定了几种模式:
- EXACTLY,
- AT_MOST,
- UNSPECIFIED。
- EXACTLY:准确的、精确的;这种模式,是最容易理解和处理的,可以理解为大小固定,比如在定义layout_width的时候,定义为固定大小 10dp,20dp,或者match_parent(此时父控件是固定的)这时候,获取出来的mode就是EXACTLY
- AT_MOST:最大的;这种模式稍微难处理些,不过也好理解,就是View的大小最大不能超过父控件,超过了,取父控件的大小,没有,则取自身大小,这种情况一般都是在layout_width设为warp_content时
- UNSPECIFIED:不指定大小,这种情况,我们几乎用不上,它是什么意思呢,就是View的大小想要多大,就给多大,不受父View的限制,几个例子就好理解了,ScrollView控件就是
我们来看看对于View的该方法我是怎么重写的
/**
* 整个view内容的宽高
*/
private int contentWidth;
private int contentHeight;
private void initView(AttributeSet attrs) {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//先利用MeasureSpec得到宽高的mode
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//利用MeasureSpec得到宽高的size
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//最后要设置的宽高
int width;
int height;
//我们期待的宽高
int desiredWidth = getPaddingLeft() + getPaddingRight() + contentWidth;
int desiredHeight = getPaddingTop() + getPaddingBottom() + contentHeight;
//根据模式来对我们需要设置的宽高进行判断
switch (widthMode) {
case MeasureSpec.AT_MOST:
width = Math.min(widthSize, desiredWidth);
break;
case MeasureSpec.UNSPECIFIED:
width = desiredWidth;
break;
default:
case MeasureSpec.EXACTLY:
width = widthSize;
break;
}
switch (heightMode) {
case MeasureSpec.AT_MOST:
height = Math.min(heightSize, desiredHeight);
break;
case MeasureSpec.UNSPECIFIED:
height = desiredHeight;
break;
default:
case MeasureSpec.EXACTLY:
height = heightSize;
break;
}
//调用父类的测量方法
setMeasuredDimension(width, height);
}
总结:
对于自定义View,只要对onMeasure和onDraw这两个方法比较熟悉的话,基本上都可以完成一些复杂View的定义,而onMeasure方法的话,只要对MeasureSpec类比较熟悉的,基本上也不会出现问题了