亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android自定義view制作絢麗的驗(yàn)證碼

 更新時(shí)間:2016年03月03日 16:26:14   作者:ydxlt  
這篇文章主要介紹了Android自定義view制作絢麗的驗(yàn)證碼的相關(guān)資料,需要的朋友可以參考下

廢話(huà)不多說(shuō)了,先給大家展示下自定義view效果圖,如果大家覺(jué)得還不錯(cuò)的話(huà),請(qǐng)繼續(xù)往下閱讀。

這里寫(xiě)圖片描述

怎么樣,這種驗(yàn)證碼是不是很常見(jiàn)呢,下面我們就自己動(dòng)手實(shí)現(xiàn)這種效果,自己動(dòng)手,豐衣足食,哈哈~

一、 自定義view的步驟

自定義view一直被認(rèn)為android進(jìn)階通向高手的必經(jīng)之路,其實(shí)自定義view好簡(jiǎn)單,自定義view真正難的是如何繪制出高難度的圖形,這需要有好的數(shù)學(xué)功底(后悔沒(méi)有好好學(xué)數(shù)學(xué)了~),因?yàn)槔L制圖形經(jīng)常要計(jì)算坐標(biāo)點(diǎn)及類(lèi)似的幾何變換等等。自定義view通常只需要以下幾個(gè)步驟:

寫(xiě)一個(gè)類(lèi)繼承View類(lèi);

重新View的構(gòu)造方法;

測(cè)量View的大小,也就是重寫(xiě)onMeasure()方法;

重新onDraw()方法。

其中第三步不是必須的,只有當(dāng)系統(tǒng)無(wú)法確定自定義的view的大小的時(shí)候需要我們自己重寫(xiě)onMeasure()方法來(lái)完成自定義view大小的測(cè)量,因?yàn)槿绻脩?hù)(程序員)在使用我們的自定義view的時(shí)候沒(méi)有指定其精確大?。▽挾然蚋叨龋纾翰季治募衛(wèi)ayout_width或layout_heigth屬性值為wrap_content而不是match_parent或某個(gè)精確的值,那么系統(tǒng)就不知道我們自定義view在onDraw()中繪制的圖形的大小,所以通常要讓我們自定義view支持wrap_content那么我們就必須重寫(xiě)onMeasure方法來(lái)告訴系統(tǒng)我們要繪制的view的大?。▽挾群透叨龋?。

還有,如果我們自定義view需要一些特殊的屬性,那么我們還需要自定義屬性,這篇文章將會(huì)涉及到自定義屬性和上面的四個(gè)步驟的內(nèi)容。

二、 自定義view的實(shí)現(xiàn)

要實(shí)現(xiàn)這種驗(yàn)證碼控件,我們需要先分析一下它要怎么實(shí)現(xiàn)。通過(guò)看上面的效果圖,我們可以知道要實(shí)現(xiàn)這種效果,首先需要在繪制驗(yàn)證碼字符串,即圖中的文本部分,然后繪制一些干擾點(diǎn),再就是繪制干擾線(xiàn)了,分析完畢。下面我們根據(jù)分析結(jié)果一步步實(shí)現(xiàn)這種效果。

1. 繼承View,重寫(xiě)構(gòu)造方法

寫(xiě)一個(gè)類(lèi)繼承View,然后重新它的構(gòu)造方法

/**
* Created by lt on 2016/3/2.
*/
public class ValidationCode extends View{
/**
* 在java代碼中創(chuàng)建view的時(shí)候調(diào)用,即new
* @param context
*/
public ValidationCode(Context context) {
this(context,null);
}

/**
* 在xml布局文件中使用view但沒(méi)有指定style的時(shí)候調(diào)用
* @param context
* @param attrs
*/
public ValidationCode(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

/**
* 在xml布局文件中使用view并指定style的時(shí)候調(diào)用
* @param context
* @param attrs
* @param defStyleAttr
*/
public ValidationCode(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 做一些初始化工作
init();
}
}

View有三個(gè)構(gòu)造方法,一般的做法都是讓一個(gè)參數(shù)和兩個(gè)參數(shù)的構(gòu)造方法調(diào)用三個(gè)構(gòu)造參數(shù)的方法,這三個(gè)構(gòu)造方法的調(diào)用情況看方法上面的注釋。在這個(gè)構(gòu)造方法里面我們先做一些初始化隨機(jī)驗(yàn)證碼字符串,畫(huà)筆等工作:

/**
* 初始化一些數(shù)據(jù)
*/
private void init() {
// 生成隨機(jī)數(shù)字和字母組合
mCodeString = getCharAndNumr(mCodeCount);
// 初始化文字畫(huà)筆
mTextPaint = new Paint();
mTextPaint.setStrokeWidth(3); // 畫(huà)筆大小為3
mTextPaint.setTextSize(mTextSize); // 設(shè)置文字大小
// 初始化干擾點(diǎn)畫(huà)筆
mPointPaint = new Paint();
mPointPaint.setStrokeWidth(6);
mPointPaint.setStrokeCap(Paint.Cap.ROUND); // 設(shè)置斷點(diǎn)處為圓形
// 初始化干擾線(xiàn)畫(huà)筆
mPathPaint = new Paint();
mPathPaint.setStrokeWidth(5);
mPathPaint.setColor(Color.GRAY);
mPathPaint.setStyle(Paint.Style.STROKE); // 設(shè)置畫(huà)筆為空心
mPathPaint.setStrokeCap(Paint.Cap.ROUND); // 設(shè)置斷點(diǎn)處為圓形
// 取得驗(yàn)證碼字符串顯示的寬度值
mTextWidth = mTextPaint.measureText(mCodeString);
}

到這里,我們就完成了自定義View步驟中的前面的兩小步了,接下來(lái)就是完成第三步,即重寫(xiě)onMeasure()進(jìn)行我們自定義view大?。▽捀撸┑臏y(cè)量了:

2. 重寫(xiě)onMeasure(),完成View大小的測(cè)量

/**
* 要像layout_width和layout_height屬性支持wrap_content就必須重新這個(gè)方法
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 分別測(cè)量控件的寬度和高度,基本為模板方法
int measureWidth = measureWidth(widthMeasureSpec);
int measureHeight = measureHeight(heightMeasureSpec);
// 其實(shí)這個(gè)方法最終會(huì)調(diào)用setMeasuredDimension(int measureWidth,int measureHeight);
// 將測(cè)量出來(lái)的寬高設(shè)置進(jìn)去完成測(cè)量
setMeasuredDimension(measureWidth, measureHeight);
}

測(cè)量寬度的方法:

/**
* 測(cè)量寬度
* @param widthMeasureSpec
*/
private int measureWidth(int widthMeasureSpec) {
int result = (int) (mTextWidth*1.8f);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if(widthMode == MeasureSpec.EXACTLY){
// 精確測(cè)量模式,即布局文件中l(wèi)ayout_width或layout_height一般為精確的值或match_parent
result = widthSize; // 既然是精確模式,那么直接返回測(cè)量的寬度即可
}else{
if(widthMode == MeasureSpec.AT_MOST) {
// 最大值模式,即布局文件中l(wèi)ayout_width或layout_height一般為wrap_content
result = Math.min(result,widthSize);
}
}
return result;
}

測(cè)量高度的方法:

/**
* 測(cè)量高度
* @param heightMeasureSpec
*/
private int measureHeight(int heightMeasureSpec) {
int result = (int) (mTextWidth/1.6f);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if(heightMode == MeasureSpec.EXACTLY){
// 精確測(cè)量模式,即布局文件中l(wèi)ayout_width或layout_height一般為精確的值或match_parent
result = heightSize; // 既然是精確模式,那么直接返回測(cè)量的寬度即可
}else{
if(heightMode == MeasureSpec.AT_MOST) {
// 最大值模式,即布局文件中l(wèi)ayout_width或layout_height一般為wrap_content
result = Math.min(result,heightSize);
}
}
return result;
}

說(shuō)明:其實(shí)onMeasure()方法最終會(huì)調(diào)用setMeasuredDimension(int measureWidth,int measureHeight);將測(cè)量出來(lái)的寬高設(shè)置進(jìn)去完成測(cè)量,而我們要做的就是測(cè)量得到寬度和高度的值,測(cè)量寬度和高度的方法最重要的就是得到當(dāng)用戶(hù)(程序員)沒(méi)有給我們的控件指定精確的值(具體數(shù)值或match_parent)時(shí)合適的寬度和高度,所以,以上測(cè)量寬度和高度的方法基本上是一個(gè)模板方法,要做的就是得到result的一個(gè)合適的值,這里我們無(wú)需關(guān)注給result的那個(gè)值,因?yàn)檫@個(gè)值根據(jù)控件算出來(lái)的一個(gè)合適的值(也許不是很合適)。

完成了控件的測(cè)量,那么接下來(lái)我們還要完成控件的繪制這一大步,也就是自定義view的核心的一步重寫(xiě)onDraw()方法繪制圖形。

3. 重寫(xiě)onDraw(),繪制圖形

根據(jù)我們上面的分析,我們需要繪制驗(yàn)證碼文本字符串,干擾點(diǎn),干擾線(xiàn)。由于干擾點(diǎn)和干擾線(xiàn)需要坐標(biāo)和路徑來(lái)繪制, 所以在繪制之前先做一些初始化隨機(jī)干擾點(diǎn)坐標(biāo)和干擾線(xiàn)路徑:

private void initData() {
// 獲取控件的寬和高,此時(shí)已經(jīng)測(cè)量完成
mHeight = getHeight();
mWidth = getWidth();
mPoints.clear();
// 生成干擾點(diǎn)坐標(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();
// 生成干擾線(xiàn)坐標(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);
}
}

有了這些數(shù)據(jù)之后,我們可以開(kāi)始繪制圖形了。

(1)繪制驗(yàn)證碼文本字符串

由于驗(yàn)證碼文本字符串是隨機(jī)生成的,所以我們需要利用代碼來(lái)隨機(jī)生成這種隨機(jī)驗(yàn)證碼:

/**
* java生成隨機(jī)數(shù)字和字母組合
* @param length[生成隨機(jī)數(shù)的長(zhǎng)度]
* @return
*/
public static String getCharAndNumr(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)) {
// 取得大寫(xiě)字母還是小寫(xiě)字母
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;
}

這種代碼是java基礎(chǔ),相信大家都看得懂,看不懂也沒(méi)關(guān)系,這種代碼網(wǎng)上隨便一搜就有,其實(shí)我也是直接從網(wǎng)上搜的,嘿嘿~。

android的2D圖形api canvas提供了drawXXX()方法來(lái)完成各種圖形的繪制,其中就有drawText()方法來(lái)繪制文本,同時(shí)還有drawPosText()在給定的坐標(biāo)點(diǎn)上繪制文本,drawTextOnPath()在給定途徑上繪制圖形。仔細(xì)觀察上面的效果圖,發(fā)現(xiàn)文本有的不是水平的,即有的被傾斜了,這就可以給我們的驗(yàn)證碼提升一定的識(shí)別難度,要實(shí)現(xiàn)文字傾斜效果,我們可以通過(guò)drawTextOnPath()在給定路徑繪制文本達(dá)到傾斜效果,然而這種方法實(shí)現(xiàn)比較困難(坐標(biāo)點(diǎn)和路徑難以計(jì)算),所以,我們可以通過(guò)canvas提供的位置變換方法rorate()結(jié)合drawText()實(shí)現(xiàn)文本傾斜效果。

int length = mCodeString.length();
float charLength = mTextWidth/length;
for(int i=1;i<=length;i++){
int offsetDegree = mRandom.nextInt(15);
// 這里只會(huì)產(chǎn)生0和1,如果是1那么正旋轉(zhuǎn)正角度,否則旋轉(zhuǎn)負(fù)角度
offsetDegree = mRandom.nextInt(2) == 1?offsetDegree:-offsetDegree;
canvas.save();
canvas.rotate(offsetDegree, mWidth / 2, mHeight / 2);
// 給畫(huà)筆設(shè)置隨機(jī)顏色,+20是為了去除一些邊界值
mTextPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20);canvas.drawText(String.valueOf(mCodeString.charAt(i - 1)), (i-1) * charLength * 1.6f+30, mHeight * 2 / 3f, mTextPaint);
canvas.restore();
}

這段代碼通過(guò)for循環(huán)分別繪制驗(yàn)證碼字符串中的每個(gè)字符,每繪制一個(gè)字符都將畫(huà)布旋轉(zhuǎn)一個(gè)隨機(jī)的正負(fù)角度,然后通過(guò)drawText()方法繪制字符,每個(gè)字符的繪制起點(diǎn)坐標(biāo)根據(jù)字符的長(zhǎng)度和位置不同而不同,這個(gè)自己計(jì)算,這里也許也不是很合適。要注意的是,每次對(duì)畫(huà)布canvas進(jìn)行位置變換的時(shí)候都要先調(diào)用canvas.save()方法保存好之前繪制的圖形,繪制結(jié)束后調(diào)用canvas.restore()恢復(fù)畫(huà)布的位置,以便下次繪制圖形的時(shí)候不會(huì)由于之前畫(huà)布的位置變化而受影響。

(2)繪制干擾點(diǎn)

// 產(chǎn)生干擾效果1 -- 干擾點(diǎn)
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);
}

給干擾點(diǎn)畫(huà)筆設(shè)置隨機(jī)顏色,然后根據(jù)隨機(jī)產(chǎn)生的點(diǎn)的坐標(biāo)利用canvas.drawPoint()繪制點(diǎn)。

(3)繪制干擾線(xiàn)

// 產(chǎn)生干擾效果2 -- 干擾線(xiàn)
for(Path path : mPaths){
mPathPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20);
canvas.drawPath(path, mPathPaint);
}

給干擾線(xiàn)畫(huà)筆設(shè)置隨機(jī)顏色,然后根據(jù)隨機(jī)產(chǎn)生路徑利用canvas.drawPath()繪制貝塞爾曲線(xiàn),從而繪制出干擾線(xiàn)。

4. 重寫(xiě)onTouchEvent,定制View事件

這里做這一步是為了實(shí)現(xiàn)當(dāng)我們點(diǎn)擊我們的自定義View的時(shí)候,完成一些操作,即定制View事件。這里,我們需要當(dāng)用戶(hù)點(diǎn)擊驗(yàn)證碼控件的時(shí)候,改變驗(yàn)證碼的文本字符串。

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// 重新生成隨機(jī)數(shù)字和字母組合
mCodeString = getCharAndNumr(mCodeCount);
invalidate();
break;
default:
break;
}
return super.onTouchEvent(event);
}

OK,到這里我們的這個(gè)自定義View就基本完成了,可能大家會(huì)問(wèn),這個(gè)自定義View是不是擴(kuò)展性太差了,定制性太低了,說(shuō)好的自定義屬性呢?跑哪里去了。不要急,下面我們就來(lái)自定義我們自己View的屬性,自定義屬性。

5. 自定義屬性,提高自定義View的可定制性

(1)在資源文件attrs.xml文件中定義我們的屬性(集)

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="IndentifyingCode">
<attr name="codeCount" format="integer|reference"></attr>
<attr name="textSize" format="dimension"></attr>
</declare-styleable>
</resources>

說(shuō)明:

在attrs.xml文件中的attr節(jié)點(diǎn)中定義我們的屬性,定義屬性需要name屬性表示我們的屬性值,同時(shí)需要format屬性表示屬性值的格式,其格式有很多種,如果屬性值可以使多種格式,那么格式間用”|”分開(kāi);

declare-styleable節(jié)點(diǎn)用來(lái)定義我們自定義屬性集,其name屬性指定了該屬性集的名稱(chēng),可以任意,但一般為自定義控件的名稱(chēng);

如果屬性已經(jīng)定義了(如layout_width),那么可以直接引用該屬性,不要指定格式了。

(2)在布局文件中引用自定義屬性,注意需要引入命名空間

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:lt="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.lt.identifyingcode.ValidationCode
android:id="@+id/validationCode"
android:layout_width="wrap_content"
android:layout_centerInParent="true"
lt:textSize="25sp"
android:background="@android:color/darker_gray"
android:layout_height="wrap_content"/>
</RelativeLayout>

引入命名空間在現(xiàn)在只需要添加xmlns:lt="http://schemas.android.com/apk/res-auto"即可(lt換成你自己的命名空間名稱(chēng)),而在以前引入命名空間方式為xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01",res后面的包路徑指的是項(xiàng)目的package`

(3)在構(gòu)造方法中獲取自定義屬性的值

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndentifyingCode);
mCodeCount = typedArray.getInteger(R.styleable.IndentifyingCode_codeCount, 5); // 獲取布局中驗(yàn)證碼位數(shù)屬性值,默認(rèn)為5個(gè)
// 獲取布局中驗(yàn)證碼文字的大小,默認(rèn)為20sp
mTextSize = typedArray.getDimension(R.styleable.IndentifyingCode_textSize, typedArray.getDimensionPixelSize(R.styleable.IndentifyingCode_textSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics())));
// 一個(gè)好的習(xí)慣是用完資源要記得回收,就想打開(kāi)數(shù)據(jù)庫(kù)和IO流用完后要記得關(guān)閉一樣
typedArray.recycle();

OK,自定義屬性也完成了,值也獲取到了,那么我們只需要將定制的屬性值在我們onDraw()繪制的時(shí)候使用到就行了,自定義屬性就是這么簡(jiǎn)單~,看到這里,也許有點(diǎn)混亂了,看一下完整代碼整理一下。

package com.lt.identifyingcode;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.Random;
/**
* Created by lt on 2016/3/2.
*/
public class ValidationCode extends View{
/**
* 控件的寬度
*/
private int mWidth;
/**
* 控件的高度
*/
private int mHeight;
/**
* 驗(yàn)證碼文本畫(huà)筆
*/
private Paint mTextPaint; // 文本畫(huà)筆
/**
* 干擾點(diǎn)坐標(biāo)的集合
*/
private ArrayList<PointF> mPoints = new ArrayList<PointF>();
private Random mRandom = new Random();;
/**
* 干擾點(diǎn)畫(huà)筆
*/
private Paint mPointPaint;
/**
* 繪制貝塞爾曲線(xiàn)的路徑集合
*/
private ArrayList<Path> mPaths = new ArrayList<Path>();
/**
* 干擾線(xiàn)畫(huà)筆
*/
private Paint mPathPaint;
/**
* 驗(yàn)證碼字符串
*/
private String mCodeString;
/**
* 驗(yàn)證碼的位數(shù)
*/
private int mCodeCount;
/**
* 驗(yàn)證碼字符的大小
*/
private float mTextSize;
/**
* 驗(yàn)證碼字符串的顯示寬度
*/
private float mTextWidth;
/**
* 在java代碼中創(chuàng)建view的時(shí)候調(diào)用,即new
* @param context
*/
public ValidationCode(Context context) {
this(context,null);
}
/**
* 在xml布局文件中使用view但沒(méi)有指定style的時(shí)候調(diào)用
* @param context
* @param attrs
*/
public ValidationCode(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* 在xml布局文件中使用view并指定style的時(shí)候調(diào)用
* @param context
* @param attrs
* @param defStyleAttr
*/
public ValidationCode(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getAttrValues(context, attrs);
// 做一些初始化工作
init();
}
/**
* 獲取布局文件中的值
* @param context
*/
private void getAttrValues(Context context,AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndentifyingCode);
mCodeCount = typedArray.getInteger(R.styleable.IndentifyingCode_codeCount, 5); // 獲取布局中驗(yàn)證碼位數(shù)屬性值,默認(rèn)為5個(gè)
// 獲取布局中驗(yàn)證碼文字的大小,默認(rèn)為20sp
mTextSize = typedArray.getDimension(R.styleable.IndentifyingCode_textSize, typedArray.getDimensionPixelSize(R.styleable.IndentifyingCode_textSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics())));
// 一個(gè)好的習(xí)慣是用完資源要記得回收,就想打開(kāi)數(shù)據(jù)庫(kù)和IO流用完后要記得關(guān)閉一樣
typedArray.recycle();
}
/**
* 要像layout_width和layout_height屬性支持wrap_content就必須重新這個(gè)方法
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 分別測(cè)量控件的寬度和高度,基本為模板方法
int measureWidth = measureWidth(widthMeasureSpec);
int measureHeight = measureHeight(heightMeasureSpec);
// 其實(shí)這個(gè)方法最終會(huì)調(diào)用setMeasuredDimension(int measureWidth,int measureHeight);
// 將測(cè)量出來(lái)的寬高設(shè)置進(jìn)去完成測(cè)量
setMeasuredDimension(measureWidth, measureHeight);
}
@Override
protected void onDraw(Canvas canvas) {
// 初始化數(shù)據(jù)
initData();
int length = mCodeString.length();
float charLength = mTextWidth/length;
for(int i=1;i<=length;i++){
int offsetDegree = mRandom.nextInt(15);
// 這里只會(huì)產(chǎn)生0和1,如果是1那么正旋轉(zhuǎn)正角度,否則旋轉(zhuǎn)負(fù)角度
offsetDegree = mRandom.nextInt(2) == 1?offsetDegree:-offsetDegree;
canvas.save();
canvas.rotate(offsetDegree, mWidth / 2, mHeight / 2);
// 給畫(huà)筆設(shè)置隨機(jī)顏色
mTextPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20);
canvas.drawText(String.valueOf(mCodeString.charAt(i - 1)), (i-1) * charLength * 1.6f+30, mHeight * 2 / 3f, mTextPaint);
canvas.restore();
}
// 產(chǎn)生干擾效果1 -- 干擾點(diǎn)
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);
}
// 產(chǎn)生干擾效果2 -- 干擾線(xiàn)
for(Path path : mPaths){
mPathPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20);
canvas.drawPath(path, mPathPaint);
}
}
private void initData() {
// 獲取控件的寬和高,此時(shí)已經(jīng)測(cè)量完成
mHeight = getHeight();
mWidth = getWidth();
mPoints.clear();
// 生成干擾點(diǎn)坐標(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();
// 生成干擾線(xiàn)坐標(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);
}
}
/**
* 初始化一些數(shù)據(jù)
*/
private void init() {
// 生成隨機(jī)數(shù)字和字母組合
mCodeString = getCharAndNumr(mCodeCount);
// 初始化文字畫(huà)筆
mTextPaint = new Paint();
mTextPaint.setStrokeWidth(3); // 畫(huà)筆大小為3
mTextPaint.setTextSize(mTextSize); // 設(shè)置文字大小
// 初始化干擾點(diǎn)畫(huà)筆
mPointPaint = new Paint();
mPointPaint.setStrokeWidth(6);
mPointPaint.setStrokeCap(Paint.Cap.ROUND); // 設(shè)置斷點(diǎn)處為圓形
// 初始化干擾線(xiàn)畫(huà)筆
mPathPaint = new Paint();
mPathPaint.setStrokeWidth(5);
mPathPaint.setColor(Color.GRAY);
mPathPaint.setStyle(Paint.Style.STROKE); // 設(shè)置畫(huà)筆為空心
mPathPaint.setStrokeCap(Paint.Cap.ROUND); // 設(shè)置斷點(diǎn)處為圓形
// 取得驗(yàn)證碼字符串顯示的寬度值
mTextWidth = mTextPaint.measureText(mCodeString);
}
/**
* java生成隨機(jī)數(shù)字和字母組合
* @param length[生成隨機(jī)數(shù)的長(zhǎng)度]
* @return
*/
public static String getCharAndNumr(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)) {
// 取得大寫(xiě)字母還是小寫(xiě)字母
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;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// 重新生成隨機(jī)數(shù)字和字母組合
mCodeString = getCharAndNumr(mCodeCount);
invalidate();
break;
default:
break;
}
return super.onTouchEvent(event);
}
/**
* 測(cè)量寬度
* @param widthMeasureSpec
*/
private int measureWidth(int widthMeasureSpec) {
int result = (int) (mTextWidth*1.8f);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if(widthMode == MeasureSpec.EXACTLY){
// 精確測(cè)量模式,即布局文件中l(wèi)ayout_width或layout_height一般為精確的值或match_parent
result = widthSize; // 既然是精確模式,那么直接返回測(cè)量的寬度即可
}else{
if(widthMode == MeasureSpec.AT_MOST) {
// 最大值模式,即布局文件中l(wèi)ayout_width或layout_height一般為wrap_content
result = Math.min(result,widthSize);
}
}
return result;
}
/**
* 測(cè)量高度
* @param heightMeasureSpec
*/
private int measureHeight(int heightMeasureSpec) {
int result = (int) (mTextWidth/1.6f);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if(heightMode == MeasureSpec.EXACTLY){
// 精確測(cè)量模式,即布局文件中l(wèi)ayout_width或layout_height一般為精確的值或match_parent
result = heightSize; // 既然是精確模式,那么直接返回測(cè)量的寬度即可
}else{
if(heightMode == MeasureSpec.AT_MOST) {
// 最大值模式,即布局文件中l(wèi)ayout_width或layout_height一般為wrap_content
result = Math.min(result,heightSize);
}
}
return result;
}
/**
* 獲取驗(yàn)證碼字符串,進(jìn)行匹配的時(shí)候只需要字符串比較即可(具體比較規(guī)則自己決定)
* @return 驗(yàn)證碼字符串
*/
public String getCodeString() {
return mCodeString;
}
}

總結(jié):這里與其說(shuō)自定義View到不如說(shuō)是繪制圖形,關(guān)鍵在于坐標(biāo)點(diǎn)的計(jì)算,這里在計(jì)算坐標(biāo)上也許不太好,以上是給大家分享Android自定義view制作絢麗的驗(yàn)證碼,希望對(duì)大家有所幫助!大家有什么好的思路或者建議希望可以留言告訴我,感激不盡~。

相關(guān)文章

最新評(píng)論