Android之自定义View,你需要了解和掌握的onMeasure测量规则

转载请注明出处: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的一个介绍和实战,各位也是掌握的差不多了,是不是已经蠢蠢欲动想要自己动手了。 这里就不贴出源码了,这篇讲的更多的理论,代码量很少。



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值