转载请注明出处:http://blog.csdn.net/blog_wang/article/details/40861927
很多刚接触Android的程序猿,觉得自定义View很难,甚至对自定义View有一种恐惧感,其实Android自定义View的实现很简单,没大家想的那么复杂,你只需要继承View,重写View构造函数、onMeasure、onDraw等几个关键函数即可,今天我将带大家了解自定义View如何来控制自己的大小。只有确定自定义View的大小时,所绘制的内容才能完美的展现。
我们先来看一个最简单的自定义View
public class MyTextView extends View {
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.parseColor("#FF7F00"));
}
}
然后将我们自定义View在布局文件中声明,这里必须要写控件的绝对路径
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.myview.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
在我们自定义的View中什么内容都没有添加,只是通过canvas对象在内容区域画了一个填充颜色,并且控件在xml中声明的宽高属性也都是内容填充,按照我们的思路项目运行起来应该什么效果都没有,此时我们运行项目看下效果图:
看到这里,相信大家就会问,为什么会出现这样的效果?控件显示区域怎么是全屏的?
如果你要认为在xml文件中将控件的宽高属性设为wrap_content,控件的大小就用内容来控制的话,那你大错特错了。这里我们由一个简单的列子来带领大家进入我们今天的主题,通过了解onMeasure的测量规则,来控制自定义View的大小。
onMeasure
measure翻译过来是测量的意思,onMeasure主要是用来测量视图的大小,该方法接受两个参数widthMeasureSpec、heightMeasureSpec,而MeasureSpec由specSize和specMode两部分组成,specSize是指在某种specMode下的参考尺寸,specMode 一共是三种类型
1、UNSPECIFIED
未指定模式:表示父容器对子View没有大小上的任何限制,可以设置成任意大小,当然这种情况会很少使用到。
2、EXACTLY
精确模式:表示父容器已经知道子View能够使用大小是多少,父容器的宽高属性一般为具体dp和match_parent。
3、AT_MOST
最大模式:表示父容器给出了一个能使用的最大空间,子View的宽高只能这么大,当然你也可以设置比这小,父容器的宽高属性一般为wrap_parent。
这里还需要说明的一点就是onMeasure方法中的MeasureSpec参数不单单是父容器来指定的。MeasureSpec其实是由父容器的MeasureSpec和View自身的LayoutParams共同决定的。
1、当View的宽高为固定的时候,如果父容器为最大模式,View的MeasureSpec为精确模式并且遵循View的LayoutParams大小,如果父容器为精确模式(固定值)那么View的MeasureSpec为精确模式,大小为父容器的剩余空间。看例子:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="100dp"
android:layout_height="100dp"
android:orientation="vertical" >
<com.example.myview.MyTextView
android:layout_width="500dp"
android:layout_height="500dp" />
</LinearLayout>
</RelativeLayout>
父容器和View本身都指定了固定值,并且View的固定值比父容器的大,但是显示的空间则为父容器的大小,将父容器的宽高指定为最大模式(wrap_content),那么效果就会按照View指定的宽高来显示。
2、当View的宽高为match_parent的时候,如果父容器的模式为精确模式,那么View的MeasureSpec为精确模式,大小为父容器的剩余空间。看例子:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@android:color/black" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.example.myview.MyTextView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout>
在这种情况下,View的大小则为父容器的剩余大小
3、当View的宽高为wrap_content的时候,不管父容器的模式是精确模式还是最大模式,View的MeasureSpec为最大模式,并且大小不能超过父容器所指定的MeasureSpec。文章一开始演示的例子就是这种类型,故不在贴代码。
相信大家对MeasureSpec的模式和决定条件都已经了解的差不多了,那我们就来动手改改我们的代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:textview="http://schemas.android.com/apk/res/com.example.myview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.example.myview.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
textview:text="我是自定义的" />
</LinearLayout>
</LinearLayout>
首先看布局文件,这里我们将自定View的宽高全部设置成wrap_content,为的就是把View确定自身的高度放到代码里面去做。
public class MyTextView extends View{
/**
* 保存字符所占的宽和高
*/
private Rect rect = new Rect();
/**
* 画笔
*/
private Paint mPaint;
/**
* 显示的字符串
*/
private String text;
/**
* 宽
*/
private int width;
/**
* 高
*/
private int height;
public MyTextView(Context context) {
super(context);
init(context);
}
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.textview);
text = arr.getString(R.styleable.textview_text);
arr.recycle();
init(context);
}
private void init(Context context){
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(20);
mPaint.setColor(Color.BLACK);
//计算字符所占空间
mPaint.getTextBounds(text,0,text.length(),rect);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//如果宽度模式为精确模式,将父容器指定的宽度赋给View宽,否则View本身计算空间
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = getPaddingLeft() + rect.width() + getPaddingRight();
}
//如果高度模式为精确模式,将父容器指定的高度赋给View高,否则View本身计算空间
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = getPaddingTop() + rect.height() + getPaddingBottom();
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.parseColor("#FF7F00"));
canvas.drawText(text,0,rect.height(),mPaint);
}
}
在构造参数中,我们获取了自定义的文本属性,然后计算出文本所占的宽高值,接着在onMeasure()方法中,我们通过判断MeasureSpec中specMode,如果为模式为精确模式时,我们不做任何处理,因为父已经计算出了我们显示的空间大小,所有也没必要自己重新去计算,只有当模式未指定和最大模式时,就需要计算View本身显示的空间大小。
这里我将宽设置为左右的padding距离加上文本的一个宽度,高设置为上下的padding距离加上文本的一个高度,然后调用setMeasuredDimension()来设置View的一个宽高,这里需要注意的是,如果我们想要自己设置View宽高的一个大小时,就必须要调用setMeasuredDimension()这个方法,通过它我们可以对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值。
在onDraw()方法中,代码很简单,利用canvas对象画出我们的文本文字。此时我们运行起来看下效果:
可以看出此时View的宽高确实是通过我们计算出来的,之所以整段文字都是紧贴着View的边框,是因为我们在xml中没有设置padding值。
好了,这篇文章到这里也要跟大家说再见了,相信通过本篇对onMeasure的一个介绍和实战,各位也是掌握的差不多了,是不是已经蠢蠢欲动想要自己动手了。 这里就不贴出源码了,这篇讲的更多的理论,代码量很少。