android 自定义view实现验证码效果(一)

此博客来自:http://blog.csdn.net/lmj623565791/article/details/24252901,感谢博客的无私奉献,在这拿来自己学习下。

自定义控件一直对我来说都比较恐怖,就此有时间好好学习下,

我们知道一个View对象要经过onMeasure()测量 ,onLayout()计算大小,onDraw()到屏幕上,然后根据你的需求看需要那方面就使用了,这是最简单的自定义view,先从最简单的做起

新建一个项目customview1

第一步:先自定义view的属性,首先在res/values/  下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。

声明属性有二种方法

第一种:

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <attr name="titleText" format="string" />  
    <attr name="titleTextColor" format="color" />  
    <attr name="titleTextSize" format="dimension" />  
    <declare-styleable name="CustomView">  
        <attr name="titleText" />  
        <attr name="titleTextColor" />  
        <attr name="titleTextSize" />  
    </declare-styleable>  
</resources>  
第二种:

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <declare-styleable name="CustomView">  
        <attr name="titleText" format="string" />  
        <attr name="titleTextColor" format="color"/>  
        <attr name="titleTextSize" format="dimension" />  
    </declare-styleable>  
</resources>  


其实都是一样的,只是把定义的属性和属性值分开写了而已,在这说下,就是怕以后有人这么写不明白,

我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型:

一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;不清楚的可以google一把。

然后在布局中声明我们的自定义View

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res/com.example.customview1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >


   <com.example.customview1.CustomView
       android:layout_width="200dp"  
        android:layout_height="100dp"  
        custom:titleText="1199"  
        custom:titleTextColor="#ff0000"  
        custom:titleTextSize="40sp" />  
</RelativeLayout>

一定要引入 xmlns:custom="http://schemas.android.com/apk/res/com.example.customview1"我们的命名空间,后面的包路径指的是项目的package

如果这里有不明白的,可以看我自定义组合控件,那讲的很清楚,在这就不做多介绍了,

第二步:在View的构造方法中,获得我们的自定义的样式

package com.example.customview1;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;


import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

public class CustomView extends View {
	/**
	 * 文本
	 */
	private String mTitleText;
	/**
	 * 文本的颜色
	 */
	private int mTitleTextColor;
	/**
	 * 文本的大小
	 */
	private int mTitleTextSize;

	/**
	 * 绘制时控制文本绘制的范围
	 */
	private Rect mBound;
	private Paint mPaint;

	public CustomView(Context context, AttributeSet attrs)
	{
		this(context, attrs, 0);
	}

	public CustomView(Context context)
	{
		this(context, null);
	}

	/**
	 * 获得我自定义的样式属性
	 * 
	 * @param context
	 * @param attrs
	 * @param defStyle
	 */
	public CustomView(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		/**
		 * 获得我们所定义的自定义样式属性
		 */
		TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, defStyle, 0);
		int n = a.getIndexCount();
		for (int i = 0; i < n; i++)
		{
			int attr = a.getIndex(i);
			switch (attr)
			{
			case R.styleable.CustomView_titleText:
				mTitleText = a.getString(attr);
				break;
			case R.styleable.CustomView_titleTextColor:
				// 默认颜色设置为黑色
				mTitleTextColor = a.getColor(attr, Color.BLACK);
				break;
			case R.styleable.CustomView_titleTextSize:
				// 默认设置为16sp,TypeValue也可以把sp转化为px
				mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
						TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
				break;

			}

		}
		a.recycle();

		/**
		 * 获得绘制文本的宽和高
		 */
		mPaint = new Paint();
		mPaint.setTextSize(mTitleTextSize);
		// mPaint.setColor(mTitleTextColor);
		mBound = new Rect();
		mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
	}


	@Override
	protected void onDraw(Canvas canvas)
	{
		mPaint.setColor(Color.YELLOW);//设置矩形颜色
		canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);//画一个矩形
		mPaint.setColor(mTitleTextColor);//设置字体颜色
		canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
	}
}

我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性

我们想要在屏幕上显示文字,那必须要重写onDraw()方法,然后通过canvas(画布)画在屏幕上

	@Override
	protected void onDraw(Canvas canvas)
	{
		mPaint.setColor(Color.YELLOW);//设置矩形颜色
		canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);//画一个矩形
		mPaint.setColor(mTitleTextColor);//设置字体颜色
		canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
	}

效果图:

技术分享

如果想让这个显示在屏幕的中间,有2个办法

1:把他的父布局为RelativeLayout,就很好搞定,

2:计算屏幕的宽和高,自身控件的宽和高,,因为在屏幕上画一个矩形其实就是定义2个坐标点,

我们自定义宽和高都是写死的在布局文件中是这么写的

  android:layout_width="200dp"  
 android:layout_height="100dp"


一般在开发中很少去这么做,那我们一般写成wrap_content,现在把宽和高改成wrap_content试试

效果出乎我们的意料:

技术分享

这是为什么呢?

所有的子view的宽和高都是父view给定的

系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。

记得在开头说过一个测量的方法onMeasure()

这个时候就要重写这个方法了,先看下View.java源码中的测量方法

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~MEASURED_DIMENSION_SET;

            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
            }

            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    }

我们看到measure()方法被final修饰了,所以这个方法不能重写,但是在阅读代码时发现

onMeasure(widthMeasureSpec, heightMeasureSpec);方法,.这就是为什么我们要重写这个方法的原因,

在onMeasure()方法中是调用了setMeasuredDimension()实现的

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

getDefaultSize()方法

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

发现一个控件的宽和高是由size和mode构成的,而mode有三个MeasureSpec.UNSPECIFIED, MeasureSpec.AT_MOST,MeasureSpec.EXACTLY

这是什么意思呢,看下面解释

MeasureSpec.UNSPECIFIED:表示子布局想要多大就多大,很少使用

MeasureSpec.AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

MeasureSpec.EXACTLY:一般是设置了明确的值或者是MATCH_PARENT


现在我们来重写onMeasure()方法

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
	{

		int width = 0;
		int height = 0;

		/**
		 * 设置宽度
		 */
		int specMode = MeasureSpec.getMode(widthMeasureSpec);//获取宽度的mode
		int specSize = MeasureSpec.getSize(widthMeasureSpec);//获取宽度大小
		
		Log.e("onMeasure","specMode="+specMode);
		Log.e("onMeasure","specSize="+specSize);
		switch (specMode)
		{
		case MeasureSpec.EXACTLY:// 明确指定了
			width = getPaddingLeft() + getPaddingRight() + specSize;
			break;
		case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
			width = getPaddingLeft() + getPaddingRight() + mBound.width();
			break;
		}

		/**
		 * 设置高度
		 */
		specMode = MeasureSpec.getMode(heightMeasureSpec);//获取高度的mode
		specSize = MeasureSpec.getSize(heightMeasureSpec);//获取高度大小
		Log.e("onMeasure","specMode="+specMode);
		Log.e("onMeasure","specSize="+specSize);
		switch (specMode)
		{
		case MeasureSpec.EXACTLY:// 明确指定了
			height = getPaddingTop() + getPaddingBottom() + specSize;
			break;
		case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
			height = getPaddingTop() + getPaddingBottom() + mBound.height();
			break;
		}
		Log.e("onMeasure","width="+width+"height="+height);
		setMeasuredDimension(width, height);//重写测量
	}

记得我们的博客标题么.要实现验证码效果,其实就是几个数字在循环的变动,这个很简单

给view设置一个点击事件:

this.setOnClickListener(new OnClickListener()
{


@Override
public void onClick(View v)
{
mTitleText = randomText();
postInvalidate();
}


});

生成四个随机数的方法

private String randomText()
{
Random random = new Random();
Set<Integer> set = new HashSet<Integer>();
while (set.size() < 4)
{
int randomInt = random.nextInt(10);
set.add(randomInt);
}
StringBuffer sb = new StringBuffer();
for (Integer i : set)
{
sb.append("" + i);
}


return sb.toString();
}

我们的验证码效果就出来了

ok 休息下,累死了













郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。