自定义view一般有以下几种方式
1、直接继承View类,需要重写onMeasure()和onDraw();
2、继承某个View的子类A,由于A已经复写了View的onMeasure()和onDraw(),所以一般只需要复写onDraw();
3、直接继承ViewGroup,需要复写onMeasure()和onLayout();
4、继承某个ViewGroup的子类,比如LinearLayout;
本文主要是讲解下第一种场景,即直接继承View复写onMeasure()和onDraw()。
你有没有想过直接继承View,复写onDraw()方法就好了, 为什么还要复写onMeasure()呢?
在学习“Android 开发艺术探索”中找到了答案。不过在源码中也是有说明的:
* Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@link #measure(int, int)} and * should be overridden by subclasses to provide accurate and efficient * measurement of their contents.* The base class implementation of measure defaults to the background size, * unless a larger size is allowed by the MeasureSpec. Subclasses should * override {@link #onMeasure(int, int)} to provide better measurements of * their content.
上面翻译过来大概意思就是:
1、View的onMeasure()方法应该被它的子类复写,并提供对控件的精确快速测量。
2、基类(即View)的测量默认实现是背景大小,除非MeasureSpec允许更大的尺寸大小。子类应该复写onMeasure()方法来提供测试控件内容更好的测量。
我们来看View的onMeasure()方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
1、参数xxxMeasureSpec是父控件施加给当前View的水平或垂直空间上的要求。对于普通View来说,xxxMeasureSpec是由父控件的xxxMeasureSpec和当前View的LayoutParam参数共同决定的。那么针对不同的父容器和View本身不同的LayoutParams,View就有多种MeasureSpec。
2、最外层的setMeasuredDimension()就是保存测量后的View的宽高。宽高最后会被保存到View的mMeasuredWidth、mMeasuredHeight,并之后在layout布局时候被使用到。
3、因此只需要看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;
}
当前View的specMode为MeasureSpec.UNSEPCIFIED时, 则宽、高为getSuggestedMinimumXxx()的值;
当前View的specMode为MeasureSpec.AT_MOST或MeasureSpec.EXACTLY时,则当前View的宽、高为specSize。这就会导致如果在当前View的布局中使用的是wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽、高等于specSize,并且这种情况下View的SpecSize是parentSize,而parentSize是父容器中目前可以使用的大小,也就是父容器当前剩余的空间大小,很显然,View的宽、高就等于父容器当前剩下的空间大小,这种效果和在布局中使用match_parent完全一致。
所以到目前为止也就知道了为什么直接继承View要复写onMeasure()方法了:
需要在onMeasure()方法中设置当使用wrap_content时View本身的大小,否则View的默认实现是wrap_content和match_parent效果一样。
解决方案的伪代码如下:
只需要给View指定一个默认的内部宽、高(mWidth和mHeight),并在wrap_content时设置此宽高即可。可以参考TextView和ImageView的源码的处理。