Android自定义组件开发之onMeasure使用

一、自定义组件造成其他组件的隐藏

我们在开发过程中往往现有的组件无法满足我们的需求,所有我们需要去自定义组件来实现我们的需求,在实现的过程中总会有各种问题,这里我们讨论一下onMeasure的使用,首先我们看一下下面的一个例子

 

[html][/html] view plaincopy

  1. <span style=”font-size:18px”><?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3.     android:layout_width=”match_parent”
  4.     android:layout_height=”match_parent”
  5.     android:orientation=”vertical” >
  6.     <com.example.test.TestMeasure
  7.         android:id=”@+id/test1″
  8.         android:layout_width=”wrap_content”
  9.         android:layout_height=”wrap_content”
  10.         />
  11.     <TextView
  12.         android:id=”@+id/test2″
  13.         android:layout_width=”wrap_content”
  14.         android:layout_height=”wrap_content”
  15.         android:text=”测试是否显示”
  16.     />
  17. </LinearLayout></span>

如果我们这样设置一个布局的话,没有运行的效果就是一片空白,如果我们调整一下位置如下:

 

 

[html][/html] view plaincopy

  1. <span style=”font-size:18px”><?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3.     android:layout_width=”match_parent”
  4.     android:layout_height=”match_parent”
  5.     android:orientation=”vertical” >
  6.     <TextView
  7.         android:id=”@+id/test2″
  8.         android:layout_width=”wrap_content”
  9.         android:layout_height=”wrap_content”
  10.         android:text=”测试是否显示”
  11.     />
  12.     <com.example.test.TestMeasure
  13.         android:id=”@+id/test1″
  14.         android:layout_width=”wrap_content”
  15.         android:layout_height=”wrap_content”
  16.         />
  17. </LinearLayout></span>

就会显示出我的TextView内容,主要原因就是我们在自定义组件的时候,没有重写重写onMeasure这个方法,我们在自定义组件的时候,大多数都是重写OnDraw方法,而忽略OnMeasure,onMeasure比OnDraw先执行,主要是测量组件的高度和宽度,然后OnDraw使用OnMeasure里面的width和height来绘制组件,有人会说我怎么没有遇到这种情况呢,要注意的是我上面设置的宽度和高度都是用在wrap_content,如果你设置了精确的值就不会出现这种情况,这个主要是涉及specMode模式,接下来我们看一下onMeasure的实现过程。

 

二、onMeasure的实现过程

我们在继承View后,然后覆写OnMeasure方法会显示

 

[java][/java] view plaincopy

  1.       @Override
  2. rotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. super.onMeasure(widthMeasureSpec, heightMeasureSpec);

会调用父类的onMeasure,内容如下:

 

 

[java][/java] view plaincopy

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2.         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  3.                 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  4.     }

onMeasure会调用两次,我们肢解一下,首先会调用

[java][/java] view plaincopy

  1. public static int getDefaultSize(int size, int measureSpec) {
  2.         int result = size;
  3.         int specMode = MeasureSpec.getMode(measureSpec);
  4.         int specSize = MeasureSpec.getSize(measureSpec);
  5.         switch (specMode) {
  6.         case MeasureSpec.UNSPECIFIED:
  7.             result = size;
  8.             break;
  9.         case MeasureSpec.AT_MOST:
  10.         case MeasureSpec.EXACTLY:
  11.             result = specSize;
  12.             break;
  13.         }
  14.         return result;
  15.     }

这里面涉及specMode,它分为三种情况:

 

MeasureSpec.UNSPECIFIED:(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;

MeasureSpec.AT_MOST:(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
MeasureSpec.EXACTLY:(至多),子元素至多达到指定大小的值。

getDefaultSize计算View组件的大小,然后使用setMeasuredDimension来设置组件的宽度和高度,我们覆写的时候,就是重写getDefaultSize这个函数的功能。

widthMeasureSpec和heightMeasureSpec这两个值是android:layout_width=”200dp” android:layout_height=”80dp”来定义的,它由两部分构成,可通过int specModeHeight = MeasureSpec.getMode(heightMeasureSpec); int specSizeHeight = MeasureSpec.getSize(heightMeasureSpec)来得到各自的值。

如果android:layout_width=”wrap_content”或android:layout_width=”fill_parent”,哪么得到的specMode为MeasureSpec.AT_MOST,如果为精确的值则为MeasureSpec.EXACTLY。另外,specSize要想得到合适的值需要在Androidmanifest.xml中添加<uses-sdk android:minSdkVersion=”10″ />

三、实例演示

自定义组件TestMeasure.java

 

[java][/java] view plaincopy

  1. package com.example.test;
  2. import android.content.Context;
  3. import android.graphics.Canvas;
  4. import android.graphics.Color;
  5. import android.graphics.Paint;
  6. import android.graphics.Paint.Style;
  7. import android.util.AttributeSet;
  8. import android.view.View;
  9. public class TestMeasure extends View {
  10.     private String text = “自定义组件”;
  11.     private Paint mPaint;
  12.     private int mAscent;
  13.     public TestMeasure(Context context, AttributeSet attrs) {
  14.         super(context, attrs);
  15.         mPaint = new Paint();
  16.         mPaint.setStyle(Style.FILL);
  17.         mPaint.setTextSize(45.0f);
  18.         mPaint.setColor(Color.RED);
  19.         setPadding(20, 60, 0, 0);
  20.     }
  21.     @Override
  22.     protected void onLayout(boolean changed, int left, int top, int right,
  23.             int bottom) {
  24.         super.onLayout(changed, left, top, right, bottom);
  25.     }
  26.     @Override
  27.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  28.         setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
  29.     }
  30.     private int measureWidth(int measureSpec){
  31.         int result = 0;
  32.         int specMode = MeasureSpec.getMode(measureSpec);
  33.         int specSize = MeasureSpec.getSize(measureSpec);
  34.         switch (specMode) {
  35.         case MeasureSpec.EXACTLY:
  36.             result = specSize;
  37.             break;
  38.         case MeasureSpec.UNSPECIFIED:
  39.             result = (int) mPaint.measureText(text) + getPaddingLeft() + getPaddingRight();
  40.             break;
  41.         case MeasureSpec.AT_MOST:
  42.             result = (int) mPaint.measureText(text) + getPaddingLeft() + getPaddingRight();
  43.             result = Math.min(result, specSize);
  44.             break;
  45.         }
  46.         return result;
  47.     }
  48.     private int measureHeight(int measureSpec){
  49.         int result = 0;
  50.         int specMode = MeasureSpec.getMode(measureSpec);
  51.         int specSize = MeasureSpec.getSize(measureSpec);
  52.         mAscent = (int) mPaint.ascent();
  53.         switch (specMode) {
  54.         case MeasureSpec.EXACTLY:
  55.             result = specSize;
  56.             break;
  57.         case MeasureSpec.UNSPECIFIED:
  58.             result = (int) (-mAscent + mPaint.descent()) + getPaddingTop() + getPaddingBottom();
  59.             break;
  60.         case MeasureSpec.AT_MOST:
  61.             result = (int) (-mAscent + mPaint.descent()) + getPaddingTop() + getPaddingBottom();
  62.             result = Math.min(result, specSize);
  63.             break;
  64.         }
  65.         return result;
  66.     }
  67.     @Override
  68.     protected void onDraw(Canvas canvas) {
  69.         canvas.drawText(text, getPaddingLeft(), getPaddingTop(), mPaint);
  70.         super.onDraw(canvas);
  71.     }
  72. }

主函数

 

 

[java][/java] view plaincopy

  1. package com.example.test;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. public class Main12 extends Activity {
  5.     @Override
  6.     protected void onCreate(Bundle savedInstanceState) {
  7.         super.onCreate(savedInstanceState);
  8.         setContentView(R.layout.main14);
  9.     }
  10. }

实现效果如下:

 

如果我们不覆写onMeasure则不会显示自定义组件下面的TextVeiw了,显示效果如下:

标签