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

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <com.example.test.TestMeasure
        android:id="@+id/test1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
	<TextView 
	    android:id="@+id/test2"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:text="测试是否显示"
	/>
</LinearLayout>


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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView 
	    android:id="@+id/test2"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:text="测试是否显示"
	/>
    <com.example.test.TestMeasure
        android:id="@+id/test1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
</LinearLayout>


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


二、onMeasure的实现过程

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

@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

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

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }



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


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;
    }



这里面涉及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

package com.example.test;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.View;

public class TestMeasure extends View {

	private String text = "自定义组件";
	private Paint mPaint;
	private int mAscent;
	
	public TestMeasure(Context context, AttributeSet attrs) {
		super(context, attrs);
		mPaint = new Paint();
		mPaint.setStyle(Style.FILL);
		mPaint.setTextSize(45.0f);
		mPaint.setColor(Color.RED);
		setPadding(20, 60, 0, 0);
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		super.onLayout(changed, left, top, right, bottom);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
	}

	private int measureWidth(int measureSpec){
		int result = 0;
		int specMode = MeasureSpec.getMode(measureSpec);  
        int specSize = MeasureSpec.getSize(measureSpec);  
        switch (specMode) {
		case MeasureSpec.EXACTLY:
			result = specSize;
			break;
		case MeasureSpec.UNSPECIFIED:
			result = (int) mPaint.measureText(text) + getPaddingLeft() + getPaddingRight(); 
			break;
		case MeasureSpec.AT_MOST:
			result = (int) mPaint.measureText(text) + getPaddingLeft() + getPaddingRight();
			result = Math.min(result, specSize);
			break;
		}
		return result;
	}
	
	private int measureHeight(int measureSpec){
		int result = 0;
		int specMode = MeasureSpec.getMode(measureSpec);  
        int specSize = MeasureSpec.getSize(measureSpec);
        mAscent = (int) mPaint.ascent();
        switch (specMode) {
		case MeasureSpec.EXACTLY:
			result = specSize;
			break;
		case MeasureSpec.UNSPECIFIED:
			result = (int) (-mAscent + mPaint.descent()) + getPaddingTop() + getPaddingBottom();
			break;
		case MeasureSpec.AT_MOST:
			result = (int) (-mAscent + mPaint.descent()) + getPaddingTop() + getPaddingBottom();
			result = Math.min(result, specSize);
			break;
		}
		return result;
	}
	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawText(text, getPaddingLeft(), getPaddingTop(), mPaint);
		super.onDraw(canvas);
	}
	
	 
}



主函数

package com.example.test;

import android.app.Activity;
import android.os.Bundle;

public class Main12 extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main14);
	}
}

实现效果如下:


Android onPageStarted1次 onPageFinished2次 android onmeasure调用两次_自定义组件


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


Android onPageStarted1次 onPageFinished2次 android onmeasure调用两次_xml_02