一、自定义组件造成其他组件的隐藏
我们在开发过程中往往现有的组件无法满足我们的需求,所有我们需要去自定义组件来实现我们的需求,在实现的过程中总会有各种问题,这里我们讨论一下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);
}
}
实现效果如下:
如果我们不覆写onMeasure则不会显示自定义组件下面的TextVeiw了,显示效果如下: