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

来源:互联网 发布:证券从业资格题库软件 编辑:程序博客网 时间:2024/05/21 10:39

转载请注明出处: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);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected 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主要是用来测量视图的大小,该方法接受两个参数widthMeasureSpecheightMeasureSpec,而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);}@Overrideprotected 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);}@Overrideprotected 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的一个介绍和实战,各位也是掌握的差不多了,是不是已经蠢蠢欲动想要自己动手了。 这里就不贴出源码了,这篇讲的更多的理论,代码量很少。



0 0