看了很多學(xué)習(xí)自定義的文章和各位大神的例子,綜合起來寫了一個驗證碼控件來練手。
如果你對自定義控件還不夠了解那么你可以先看鴻洋的這篇文章來學(xué)習(xí)基本的知識。
先來一張效果圖

效果圖.gif
- 1.先自定義兩個屬性用來設(shè)置view的字體大小和驗證碼的個數(shù)。
<declare-styleable name="VerificationCodeView">
<attr name="textSize" format="dimension" />
<attr name="textCount" format="integer" />
</declare-styleable>
- 2.在代碼中獲取屬性值
public VerificationCodeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VerificationCodeView);
mTextSize = a.getDimensionPixelSize(R.styleable.VerificationCodeView_textSize, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
mTextCount = a.getInt(R.styleable.VerificationCodeView_textCount, 4);
a.recycle();
}
在這里著重說明一點:用完TypedArray一定要記得 調(diào)用recycle()方法釋放資源
- 3.測量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//測量寬度
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
mWidth = specSize;
} else {
if (specMode == MeasureSpec.AT_MOST) {
mWidth = Math.min((int) (mTextWidth * 1.8f), specSize);
}
}
//測量高度
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
mHeight = specSize;
} else {
if (specMode == MeasureSpec.AT_MOST) {
mHeight = Math.min((int) (mTextWidth * 16f), specSize);
}
}
setMeasuredDimension(mWidth, mHeight);
}
- 4.初始化畫筆
private void init() {
mText = getCharAndNum(mTextCount);
//初始化驗證碼畫筆
mTextPaint = new Paint();
mTextPaint.setStrokeWidth(3);
mTextPaint.setTextSize(mTextSize);
//初始化干擾點畫筆
mPointPaint = new Paint();
mPointPaint.setStrokeWidth(6);
mPointPaint.setStrokeCap(Paint.Cap.ROUND); // 設(shè)置斷點處為圓形
//初始化干擾線畫筆
mPathPaint = new Paint();
mPathPaint.setStrokeWidth(5);
mPathPaint.setColor(Color.GRAY);
mPathPaint.setStyle(Paint.Style.STROKE); // 設(shè)置畫筆為空心
mPathPaint.setStrokeCap(Paint.Cap.ROUND); // 設(shè)置斷點處為圓形
// 取得驗證碼字符串顯示的寬度值
mTextWidth = mTextPaint.measureText(mText);
}
初始化最好不用放在onDraw()方法里,在實際過程中onDraw()方法可能會被多次調(diào)用。
- 5.初始化數(shù)據(jù)
private void initData() {
mPoints.clear();
// 生成干擾點坐標(biāo)
for (int i = 0; i < 150; i++) {
PointF pointF = new PointF(mRandom.nextInt(mWidth) + 10, mRandom.nextInt(mHeight) + 10);
mPoints.add(pointF);
}
mPaths.clear();
// 生成干擾線坐標(biāo)
for (int i = 0; i < 2; i++) {
Path path = new Path();
int startX = mRandom.nextInt(mWidth / 3) + 10;
int startY = mRandom.nextInt(mHeight / 3) + 10;
int endX = mRandom.nextInt(mWidth / 2) + mWidth / 2 - 10;
int endY = mRandom.nextInt(mHeight / 2) + mHeight / 2 - 10;
path.moveTo(startX, startY);
path.quadTo(Math.abs(endX - startX) / 2, Math.abs(endY - startY) / 2, endX, endY);
mPaths.add(path);
}
}
- 6.最后一步,繪制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setBackgroundColor(Color.GRAY);
initData();
float charLength = mTextWidth / mTextCount;
//繪制驗證碼
for (int i = 1; i <= mTextCount; i++) {
int offsetDegree = mRandom.nextInt(15);
// 這里只會產(chǎn)生0和1,如果是1那么正旋轉(zhuǎn)正角度,否則旋轉(zhuǎn)負(fù)角度
offsetDegree = mRandom.nextInt(2) == 1 ? offsetDegree : -offsetDegree;
canvas.save();
//旋轉(zhuǎn)畫布
canvas.rotate(offsetDegree, mWidth / 2, mHeight / 2);
// 給畫筆設(shè)置隨機顏色
mTextPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20);
canvas.drawText(String.valueOf(mText.charAt(i - 1)), (i - 1) * charLength * 1.6f + 30, mHeight * 2 / 3f, mTextPaint);
//畫完一定要把畫布轉(zhuǎn)回原來的位置,不然會影響后面的繪制
canvas.restore();
}
// 繪制干擾點
for (PointF pointF : mPoints) {
mPointPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20);
canvas.drawPoint(pointF.x, pointF.y, mPointPaint);
}
//繪制干擾線
for (Path path : mPaths) {
mPathPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20);
canvas.drawPath(path, mPathPaint);
}
}
- 7.隨機生成字符串和數(shù)值
/**
* 隨機生成字符串和數(shù)值
*
* @param length length
* @return String
*/
public static String getCharAndNum(int length) {
String val = "";
Random random = new Random();
for (int i = 0; i < length; i++) {
// 輸出字母還是數(shù)字
String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
// 字符串
if ("char".equalsIgnoreCase(charOrNum)) {
// 取得大寫字母還是小寫字母
int choice = random.nextInt(2) % 2 == 0 ? 65 : 97;
val += (char) (choice + random.nextInt(26));
} else if ("num".equalsIgnoreCase(charOrNum)) { // 數(shù)字
val += String.valueOf(random.nextInt(10));
}
}
return val;
}
后記,Android自定義View并沒有想象中的那么可怕和恐怖,只要能靜下心來去思考多在紙上寫寫畫畫就能發(fā)現(xiàn)其實并沒有多難,網(wǎng)上各大論壇都有很優(yōu)秀的博客和教程,我們并不是獨自戰(zhàn)斗,而是站在巨人的肩膀上前行,共勉之。附一張我學(xué)習(xí)自定義View的手稿,難堪的字體可以忽略。

鬼畫桃符.jpg