效果图
知识点主要有:
获取自定义的属性值
计算View大小
绘制
生成验证码
回调
1.获取自定义属性
/**
* 获取自定义属性
*
* @param context
* @param attrs
* @param defStyle
*/
private void obtainCustomAttribute(Context context,AttributeSet attrs,int defStyle)
{
/**
* 获得我们所定义的自定义样式属性
*/
TypedArray typedArray = context.getTheme()
.obtainStyledAttributes(attrs,R.styleable.ValidateCodeView,defStyle,0);
//遍历获取自定义属性值
for(int i = 0;i < typedArray.getIndexCount();i++)
{
//当前属性
int attribute = typedArray.getIndex(i);
switch(attribute)
{
//验证码颜色 默认黑色
case R.styleable.ValidateCodeView_codeColor:
mCodeColor = typedArray.getColor(attribute,Color.parseColor(DEFAULT_CODE_COLOR));
break;
//验证码背景色 默认白色
case R.styleable.ValidateCodeView_codeBackground:
mCodeBackground = typedArray.getColor(attribute,Color.parseColor(DEFAULT_CODE_BACKGROUND));
break;
//验证码字体大小,默认16sp
case R.styleable.ValidateCodeView_codeSize:
mCodeSize = sp2px(typedArray,attribute);
break;
//验证码长度,默认为6
case R.styleable.ValidateCodeView_codeLength:
mCodeLength = typedArray.getInt(attribute,DEFAULT_CODE_LENGTH);
break;
}
}
typedArray.recycle();
}
/**
* sp转换成px
*
* @param typedArray
* @return
*/
private int sp2px(TypedArray typedArray,int index)
{
DisplayMetrics metrics = getResources().getDisplayMetrics();
//默认值
int defaultValue = (int)TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,DEFAULT_TEXT_SIZE,metrics);
return typedArray.getDimensionPixelSize(index,defaultValue);
}
2.计算View大小
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec)
{
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = getActualWidth(widthMode,widthSize);
int height = getActualHeight(heightMode,heightSize);
//重新设置属性
setMeasuredDimension(width,height);
}
/**
* 获取到实际的宽度
*
* @param widthMode
* @param widthSize
* @return
*/
private int getActualWidth(int widthMode,int widthSize)
{
mPaintCode.getTextBounds(mCurrentCode,0,mCurrentCode.length(),mCodeBounds);
//精确的宽度
if(widthMode == MeasureSpec.EXACTLY)
return widthSize;
//wrap_content
//测量出验证码的宽度
float textWidth = mCodeBounds.width();
return (int)(getPaddingLeft() + textWidth + getPaddingRight());
}
/**
* 获取到实际的高度
*
* @param heightMode
* @param heightSzie
* @return
*/
private int getActualHeight(int heightMode,int heightSzie)
{
mPaintCode.getTextBounds(mCurrentCode,0,mCurrentCode.length(),mCodeBounds);
//精确的高度
if(heightMode == MeasureSpec.EXACTLY)
return heightSzie;
//wrap_content
//测量出验证码的高度
float textHeight = mCodeBounds.height();
return (int)(getPaddingTop() + textHeight + getPaddingBottom());
}
3.绘制
/**
* 绘制方法
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas)
{
//绘制背景
canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaintBackground);
//绘制验证码
float x = getWidth() / 2 - mCodeBounds.width() / 2;
float y = getHeight() / 2 + mCodeBounds.height() / 2;
canvas.drawText(mCurrentCode,x,y,mPaintCode);
}
4.生成验证码
//随机字符集
private static final char[] RANDOM_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
};
/**
* 改变验证码
*/
private void changeValidateCode()
{
mCurrentCode = getValidateCode();
//请求重新测量(不同的验证码字符串大小也不一样)
requestLayout();
//刷新
invalidate();
}
/**
* 获取验证码
*
* @return
*/
private String getValidateCode()
{
//验证码
char[] result =new char[mCodeLength];
Random random = new Random();
//随机获取,并填充
for(int i=0;i<mCodeLength;i++)
{
result[i] = RANDOM_CHARS[random.nextInt(RANDOM_CHARS.length)];
}
String code = new String(result);
//回调
if(mListener != null)
mListener.changed(code);
return code;
}
5.回调
/********************************************************
*
* 生成随机数完成回调接口
*
********************************************************
*/
public interface ValidateCodeChangedListener
{
void changed(String validateCode);
}
private ValidateCodeChangedListener mListener;
public void setValidateCodeChangedListener(ValidateCodeChangedListener listener)
{
mListener = listener;
}
全部代码:
package jfsl.customview.view;
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.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import java.util.Random;
import jfsl.customview.R;
/**
* 验证码View
* 1.文字大小
* 2.颜色
* 3.背景色
* 4.验证码长度
********************************************
* 未完成:
* 1.添加干扰线
* 2.改变角度
********************************************
* @author JFSL
* @version 1.0.0
* @date 2017-04-29 20:05
*/
public class ValidateCodeView extends View
{
public static final int DEFAULT_TEXT_SIZE = 16;
public static final int DEFAULT_CODE_LENGTH = 6;
public static final String DEFAULT_CODE_COLOR = "#444444";
public static final String DEFAULT_CODE_BACKGROUND = "#FAFAFA";
//随机字符集
private static final char[] RANDOM_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
};
//当前验证码
private String mCurrentCode;
//验证码文字大小
private int mCodeSize;
//验证码颜色
private int mCodeColor;
//验证码背景色
private int mCodeBackground;
//验证码长度
private int mCodeLength;
//验证码的画笔
private Paint mPaintCode;
//验证码背景色的画笔
private Paint mPaintBackground;
//验证的边界
private Rect mCodeBounds;
public ValidateCodeView(Context context)
{
this(context,null);
}
public ValidateCodeView(Context context,@Nullable AttributeSet attrs)
{
this(context,attrs,0);
}
/**
* 获得我自定义的样式属性
*
* @param context
* @param attrs
* @param defStyle
*/
public ValidateCodeView(Context context,AttributeSet attrs,int defStyle)
{
super(context,attrs,defStyle);
//获取自定义属性
obtainCustomAttribute(context,attrs,defStyle);
initDatas();
initEvent();
}
/**
* 获取自定义属性
*
* @param context
* @param attrs
* @param defStyle
*/
private void obtainCustomAttribute(Context context,AttributeSet attrs,int defStyle)
{
/**
* 获得我们所定义的自定义样式属性
*/
TypedArray typedArray = context.getTheme()
.obtainStyledAttributes(attrs,R.styleable.ValidateCodeView,defStyle,0);
//遍历获取自定义属性值
for(int i = 0;i < typedArray.getIndexCount();i++)
{
//当前属性
int attribute = typedArray.getIndex(i);
switch(attribute)
{
//验证码颜色 默认黑色
case R.styleable.ValidateCodeView_codeColor:
mCodeColor = typedArray.getColor(attribute,Color.parseColor(DEFAULT_CODE_COLOR));
break;
//验证码背景色 默认白色
case R.styleable.ValidateCodeView_codeBackground:
mCodeBackground = typedArray.getColor(attribute,Color.parseColor(DEFAULT_CODE_BACKGROUND));
break;
//验证码字体大小,默认16sp
case R.styleable.ValidateCodeView_codeSize:
mCodeSize = sp2px(typedArray,attribute);
break;
//验证码长度,默认为6
case R.styleable.ValidateCodeView_codeLength:
mCodeLength = typedArray.getInt(attribute,DEFAULT_CODE_LENGTH);
break;
}
}
typedArray.recycle();
}
/**
* 初始化数据
*/
private void initDatas()
{
//获取验证码(初始化,计算边界用)
mCurrentCode = getValidateCode();
//初始化画笔类
initPaint();
}
/**
* 设置点击监听
*/
private void initEvent()
{
this.setOnClickListener(mThisClickListener);
}
/**
* 初始化画笔类
*/
private void initPaint()
{
//绘制验证码的画笔
mPaintCode = new Paint();
mPaintCode.setTextSize(mCodeSize);
mPaintCode.setColor(mCodeColor);
//绘制背景色的画笔
mPaintBackground = new Paint();
mPaintBackground.setColor(mCodeBackground);
//初始化边界
mCodeBounds = new Rect();
}
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec)
{
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = getActualWidth(widthMode,widthSize);
int height = getActualHeight(heightMode,heightSize);
//重新设置属性
setMeasuredDimension(width,height);
}
/**
* 绘制方法
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas)
{
//绘制背景
canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaintBackground);
//绘制验证码
float x = getWidth() / 2 - mCodeBounds.width() / 2;
float y = getHeight() / 2 + mCodeBounds.height() / 2;
canvas.drawText(mCurrentCode,x,y,mPaintCode);
}
/**
* 获取到实际的宽度
*
* @param widthMode
* @param widthSize
* @return
*/
private int getActualWidth(int widthMode,int widthSize)
{
mPaintCode.getTextBounds(mCurrentCode,0,mCurrentCode.length(),mCodeBounds);
//精确的宽度
if(widthMode == MeasureSpec.EXACTLY)
return widthSize;
//wrap_content
//测量出验证码的宽度
float textWidth = mCodeBounds.width();
return (int)(getPaddingLeft() + textWidth + getPaddingRight());
}
/**
* 获取到实际的高度
*
* @param heightMode
* @param heightSzie
* @return
*/
private int getActualHeight(int heightMode,int heightSzie)
{
mPaintCode.getTextBounds(mCurrentCode,0,mCurrentCode.length(),mCodeBounds);
//精确的高度
if(heightMode == MeasureSpec.EXACTLY)
return heightSzie;
//wrap_content
//测量出验证码的高度
float textHeight = mCodeBounds.height();
return (int)(getPaddingTop() + textHeight + getPaddingBottom());
}
/**
* sp转换成px
*
* @param typedArray
* @return
*/
private int sp2px(TypedArray typedArray,int index)
{
DisplayMetrics metrics = getResources().getDisplayMetrics();
//默认值
int defaultValue = (int)TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,DEFAULT_TEXT_SIZE,metrics);
return typedArray.getDimensionPixelSize(index,defaultValue);
}
/**
* 当前对象点击监听
*/
private OnClickListener mThisClickListener = new OnClickListener()
{
@Override
public void onClick(View v)
{
changeValidateCode();
}
};
/**
* 改变验证码
*/
private void changeValidateCode()
{
mCurrentCode = getValidateCode();
//请求重新测量(不同的验证码字符串大小也不一样)
requestLayout();
//刷新
invalidate();
}
/**
* 获取验证码
*
* @return
*/
private String getValidateCode()
{
//验证码
char[] result =new char[mCodeLength];
Random random = new Random();
//随机获取,并填充
for(int i=0;i<mCodeLength;i++)
{
result[i] = RANDOM_CHARS[random.nextInt(RANDOM_CHARS.length)];
}
String code = new String(result);
//回调
if(mListener != null)
mListener.changed(code);
return code;
}
/********************************************************
*
* 生成随机数完成回调接口
*
********************************************************
*/
public interface ValidateCodeChangedListener
{
void changed(String validateCode);
}
private ValidateCodeChangedListener mListener;
public void setValidateCodeChangedListener(ValidateCodeChangedListener listener)
{
mListener = listener;
}
}
属性文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--验证码颜色-->
<attr name="codeColor" format="color" />
<!--验证码背景色-->
<attr name="codeBackground" format="color" />
<!--验证码大小-->
<attr name="codeSize" format="dimension" />
<!--验证码长度-->
<attr name="codeLength" format="integer" />
<declare-styleable name="ValidateCodeView">
<attr name="codeColor" />
<attr name="codeBackground" />
<attr name="codeSize" />
<attr name="codeLength" />
</declare-styleable>
</resources>
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:orientation="vertical">
<jfsl.customview.view.ValidateCodeView
android:id="@+id/id_vcv_my"
android:layout_width="100dp"
android:layout_height="50dp"
app:codeColor="#FAFAFA"
app:codeBackground="#009688"
app:codeLength="4"
app:codeSize="18sp"/>
<EditText
android:id="@+id/id_edittext_code"
android:layout_width="match_parent"
android:layout_height="56dp"/>
<Button
android:id="@+id/id_button_validate"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="验证"/>
</LinearLayout>
测试代码:
package jfsl.customview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import jfsl.customview.view.ValidateCodeView;
public class ActivityMain extends AppCompatActivity
{
private ValidateCodeView mValidateCodeView;
private EditText mEditText;
private Button mButton;
private String mCode;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
initDatas();
initEvent();
}
private void initViews()
{
mValidateCodeView = (ValidateCodeView)findViewById(R.id.id_vcv_my);
mEditText = (EditText)findViewById(R.id.id_edittext_code);
mButton = (Button)findViewById(R.id.id_button_validate);
}
private void initDatas()
{
}
private void initEvent()
{
mValidateCodeView.setValidateCodeChangedListener(
new ValidateCodeView.ValidateCodeChangedListener()
{
@Override
public void changed(String validateCode)
{
//转成成大写
mCode = validateCode.toUpperCase();
}
});
mButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
validate();
}
});
}
private void validate()
{
String code = mEditText.getText().toString().trim();
String upperCode = code.toUpperCase();
if(upperCode.equals(mCode))
{
Toast.makeText(this,"验证码正确",Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(this,"验证码错误",Toast.LENGTH_SHORT).show();
}
}
参考代码 http://blog.csdn.net/lmj623565791/article/details/24252901/