Android自定义组件开发之onMeasure使用
一、自定义组件造成其他组件的隐藏
我们在开发过程中往往现有的组件无法满足我们的需求,所有我们需要去自定义组件来实现我们的需求,在实现的过程中总会有各种问题,这里我们讨论一下onMeasure的使用,首先我们看一下下面的一个例子
- <span style=”font-size:18px”><?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></span>
如果我们这样设置一个布局的话,没有运行的效果就是一片空白,如果我们调整一下位置如下:
- <span style=”font-size:18px”><?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></span>
就会显示出我的TextView内容,主要原因就是我们在自定义组件的时候,没有重写重写onMeasure这个方法,我们在自定义组件的时候,大多数都是重写OnDraw方法,而忽略OnMeasure,onMeasure比OnDraw先执行,主要是测量组件的高度和宽度,然后OnDraw使用OnMeasure里面的width和height来绘制组件,有人会说我怎么没有遇到这种情况呢,要注意的是我上面设置的宽度和高度都是用在wrap_content,如果你设置了精确的值就不会出现这种情况,这个主要是涉及specMode模式,接下来我们看一下onMeasure的实现过程。
二、onMeasure的实现过程
我们在继承View后,然后覆写OnMeasure方法会显示
- @Override
- rotected 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了,显示效果如下: