Android自定義view制作絢麗的驗證碼
廢話不多說了,先給大家展示下自定義view效果圖,如果大家覺得還不錯的話,請繼續(xù)往下閱讀。
怎么樣,這種驗證碼是不是很常見呢,下面我們就自己動手實現(xiàn)這種效果,自己動手,豐衣足食,哈哈~
一、 自定義view的步驟
自定義view一直被認為android進階通向高手的必經(jīng)之路,其實自定義view好簡單,自定義view真正難的是如何繪制出高難度的圖形,這需要有好的數(shù)學(xué)功底(后悔沒有好好學(xué)數(shù)學(xué)了~),因為繪制圖形經(jīng)常要計算坐標(biāo)點及類似的幾何變換等等。自定義view通常只需要以下幾個步驟:
寫一個類繼承View類;
重新View的構(gòu)造方法;
測量View的大小,也就是重寫onMeasure()方法;
重新onDraw()方法。
其中第三步不是必須的,只有當(dāng)系統(tǒng)無法確定自定義的view的大小的時候需要我們自己重寫onMeasure()方法來完成自定義view大小的測量,因為如果用戶(程序員)在使用我們的自定義view的時候沒有指定其精確大?。▽挾然蚋叨龋?,如:布局文件中l(wèi)ayout_width或layout_heigth屬性值為wrap_content而不是match_parent或某個精確的值,那么系統(tǒng)就不知道我們自定義view在onDraw()中繪制的圖形的大小,所以通常要讓我們自定義view支持wrap_content那么我們就必須重寫onMeasure方法來告訴系統(tǒng)我們要繪制的view的大小(寬度和高度)。
還有,如果我們自定義view需要一些特殊的屬性,那么我們還需要自定義屬性,這篇文章將會涉及到自定義屬性和上面的四個步驟的內(nèi)容。
二、 自定義view的實現(xiàn)
要實現(xiàn)這種驗證碼控件,我們需要先分析一下它要怎么實現(xiàn)。通過看上面的效果圖,我們可以知道要實現(xiàn)這種效果,首先需要在繪制驗證碼字符串,即圖中的文本部分,然后繪制一些干擾點,再就是繪制干擾線了,分析完畢。下面我們根據(jù)分析結(jié)果一步步實現(xiàn)這種效果。
1. 繼承View,重寫構(gòu)造方法
寫一個類繼承View,然后重新它的構(gòu)造方法
/** * Created by lt on 2016/3/2. */ public class ValidationCode extends View{ /** * 在java代碼中創(chuàng)建view的時候調(diào)用,即new * @param context */ public ValidationCode(Context context) { this(context,null); } /** * 在xml布局文件中使用view但沒有指定style的時候調(diào)用 * @param context * @param attrs */ public ValidationCode(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * 在xml布局文件中使用view并指定style的時候調(diào)用 * @param context * @param attrs * @param defStyleAttr */ public ValidationCode(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // 做一些初始化工作 init(); } }
View有三個構(gòu)造方法,一般的做法都是讓一個參數(shù)和兩個參數(shù)的構(gòu)造方法調(diào)用三個構(gòu)造參數(shù)的方法,這三個構(gòu)造方法的調(diào)用情況看方法上面的注釋。在這個構(gòu)造方法里面我們先做一些初始化隨機驗證碼字符串,畫筆等工作:
/** * 初始化一些數(shù)據(jù) */ private void init() { // 生成隨機數(shù)字和字母組合 mCodeString = getCharAndNumr(mCodeCount); // 初始化文字畫筆 mTextPaint = new Paint(); mTextPaint.setStrokeWidth(3); // 畫筆大小為3 mTextPaint.setTextSize(mTextSize); // 設(shè)置文字大小 // 初始化干擾點畫筆 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(mCodeString); }
到這里,我們就完成了自定義View步驟中的前面的兩小步了,接下來就是完成第三步,即重寫onMeasure()進行我們自定義view大?。▽捀撸┑臏y量了:
2. 重寫onMeasure(),完成View大小的測量
/** * 要像layout_width和layout_height屬性支持wrap_content就必須重新這個方法 * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 分別測量控件的寬度和高度,基本為模板方法 int measureWidth = measureWidth(widthMeasureSpec); int measureHeight = measureHeight(heightMeasureSpec); // 其實這個方法最終會調(diào)用setMeasuredDimension(int measureWidth,int measureHeight); // 將測量出來的寬高設(shè)置進去完成測量 setMeasuredDimension(measureWidth, measureHeight); }
測量寬度的方法:
/** * 測量寬度 * @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){ // 精確測量模式,即布局文件中l(wèi)ayout_width或layout_height一般為精確的值或match_parent result = widthSize; // 既然是精確模式,那么直接返回測量的寬度即可 }else{ if(widthMode == MeasureSpec.AT_MOST) { // 最大值模式,即布局文件中l(wèi)ayout_width或layout_height一般為wrap_content result = Math.min(result,widthSize); } } return result; }
測量高度的方法:
/** * 測量高度 * @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){ // 精確測量模式,即布局文件中l(wèi)ayout_width或layout_height一般為精確的值或match_parent result = heightSize; // 既然是精確模式,那么直接返回測量的寬度即可 }else{ if(heightMode == MeasureSpec.AT_MOST) { // 最大值模式,即布局文件中l(wèi)ayout_width或layout_height一般為wrap_content result = Math.min(result,heightSize); } } return result; }
說明:其實onMeasure()方法最終會調(diào)用setMeasuredDimension(int measureWidth,int measureHeight);將測量出來的寬高設(shè)置進去完成測量,而我們要做的就是測量得到寬度和高度的值,測量寬度和高度的方法最重要的就是得到當(dāng)用戶(程序員)沒有給我們的控件指定精確的值(具體數(shù)值或match_parent)時合適的寬度和高度,所以,以上測量寬度和高度的方法基本上是一個模板方法,要做的就是得到result的一個合適的值,這里我們無需關(guān)注給result的那個值,因為這個值根據(jù)控件算出來的一個合適的值(也許不是很合適)。
完成了控件的測量,那么接下來我們還要完成控件的繪制這一大步,也就是自定義view的核心的一步重寫onDraw()方法繪制圖形。
3. 重寫onDraw(),繪制圖形
根據(jù)我們上面的分析,我們需要繪制驗證碼文本字符串,干擾點,干擾線。由于干擾點和干擾線需要坐標(biāo)和路徑來繪制, 所以在繪制之前先做一些初始化隨機干擾點坐標(biāo)和干擾線路徑:
private void initData() { // 獲取控件的寬和高,此時已經(jīng)測量完成 mHeight = getHeight(); mWidth = getWidth(); 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); } }
有了這些數(shù)據(jù)之后,我們可以開始繪制圖形了。
(1)繪制驗證碼文本字符串
由于驗證碼文本字符串是隨機生成的,所以我們需要利用代碼來隨機生成這種隨機驗證碼:
/** * java生成隨機數(shù)字和字母組合 * @param length[生成隨機數(shù)的長度] * @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)) { // 取得大寫字母還是小寫字母 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ǔ),相信大家都看得懂,看不懂也沒關(guān)系,這種代碼網(wǎng)上隨便一搜就有,其實我也是直接從網(wǎng)上搜的,嘿嘿~。
android的2D圖形api canvas提供了drawXXX()方法來完成各種圖形的繪制,其中就有drawText()方法來繪制文本,同時還有drawPosText()在給定的坐標(biāo)點上繪制文本,drawTextOnPath()在給定途徑上繪制圖形。仔細觀察上面的效果圖,發(fā)現(xiàn)文本有的不是水平的,即有的被傾斜了,這就可以給我們的驗證碼提升一定的識別難度,要實現(xiàn)文字傾斜效果,我們可以通過drawTextOnPath()在給定路徑繪制文本達到傾斜效果,然而這種方法實現(xiàn)比較困難(坐標(biāo)點和路徑難以計算),所以,我們可以通過canvas提供的位置變換方法rorate()結(jié)合drawText()實現(xiàn)文本傾斜效果。
int length = mCodeString.length(); float charLength = mTextWidth/length; for(int i=1;i<=length;i++){ int offsetDegree = mRandom.nextInt(15); // 這里只會產(chǎn)生0和1,如果是1那么正旋轉(zhuǎn)正角度,否則旋轉(zhuǎn)負角度 offsetDegree = mRandom.nextInt(2) == 1?offsetDegree:-offsetDegree; canvas.save(); canvas.rotate(offsetDegree, mWidth / 2, mHeight / 2); // 給畫筆設(shè)置隨機顏色,+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(); }
這段代碼通過for循環(huán)分別繪制驗證碼字符串中的每個字符,每繪制一個字符都將畫布旋轉(zhuǎn)一個隨機的正負角度,然后通過drawText()方法繪制字符,每個字符的繪制起點坐標(biāo)根據(jù)字符的長度和位置不同而不同,這個自己計算,這里也許也不是很合適。要注意的是,每次對畫布canvas進行位置變換的時候都要先調(diào)用canvas.save()方法保存好之前繪制的圖形,繪制結(jié)束后調(diào)用canvas.restore()恢復(fù)畫布的位置,以便下次繪制圖形的時候不會由于之前畫布的位置變化而受影響。
(2)繪制干擾點
// 產(chǎn)生干擾效果1 -- 干擾點 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); }
給干擾點畫筆設(shè)置隨機顏色,然后根據(jù)隨機產(chǎn)生的點的坐標(biāo)利用canvas.drawPoint()繪制點。
(3)繪制干擾線
// 產(chǎn)生干擾效果2 -- 干擾線 for(Path path : mPaths){ mPathPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20); canvas.drawPath(path, mPathPaint); }
給干擾線畫筆設(shè)置隨機顏色,然后根據(jù)隨機產(chǎn)生路徑利用canvas.drawPath()繪制貝塞爾曲線,從而繪制出干擾線。
4. 重寫onTouchEvent,定制View事件
這里做這一步是為了實現(xiàn)當(dāng)我們點擊我們的自定義View的時候,完成一些操作,即定制View事件。這里,我們需要當(dāng)用戶點擊驗證碼控件的時候,改變驗證碼的文本字符串。
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: // 重新生成隨機數(shù)字和字母組合 mCodeString = getCharAndNumr(mCodeCount); invalidate(); break; default: break; } return super.onTouchEvent(event); }
OK,到這里我們的這個自定義View就基本完成了,可能大家會問,這個自定義View是不是擴展性太差了,定制性太低了,說好的自定義屬性呢?跑哪里去了。不要急,下面我們就來自定義我們自己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>
說明:
在attrs.xml文件中的attr節(jié)點中定義我們的屬性,定義屬性需要name屬性表示我們的屬性值,同時需要format屬性表示屬性值的格式,其格式有很多種,如果屬性值可以使多種格式,那么格式間用”|”分開;
declare-styleable節(jié)點用來定義我們自定義屬性集,其name屬性指定了該屬性集的名稱,可以任意,但一般為自定義控件的名稱;
如果屬性已經(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換成你自己的命名空間名稱),而在以前引入命名空間方式為xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01",res后面的包路徑指的是項目的package`
(3)在構(gòu)造方法中獲取自定義屬性的值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndentifyingCode); mCodeCount = typedArray.getInteger(R.styleable.IndentifyingCode_codeCount, 5); // 獲取布局中驗證碼位數(shù)屬性值,默認為5個 // 獲取布局中驗證碼文字的大小,默認為20sp mTextSize = typedArray.getDimension(R.styleable.IndentifyingCode_textSize, typedArray.getDimensionPixelSize(R.styleable.IndentifyingCode_textSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()))); // 一個好的習(xí)慣是用完資源要記得回收,就想打開數(shù)據(jù)庫和IO流用完后要記得關(guān)閉一樣 typedArray.recycle();
OK,自定義屬性也完成了,值也獲取到了,那么我們只需要將定制的屬性值在我們onDraw()繪制的時候使用到就行了,自定義屬性就是這么簡單~,看到這里,也許有點混亂了,看一下完整代碼整理一下。
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; /** * 驗證碼文本畫筆 */ private Paint mTextPaint; // 文本畫筆 /** * 干擾點坐標(biāo)的集合 */ private ArrayList<PointF> mPoints = new ArrayList<PointF>(); private Random mRandom = new Random();; /** * 干擾點畫筆 */ private Paint mPointPaint; /** * 繪制貝塞爾曲線的路徑集合 */ private ArrayList<Path> mPaths = new ArrayList<Path>(); /** * 干擾線畫筆 */ private Paint mPathPaint; /** * 驗證碼字符串 */ private String mCodeString; /** * 驗證碼的位數(shù) */ private int mCodeCount; /** * 驗證碼字符的大小 */ private float mTextSize; /** * 驗證碼字符串的顯示寬度 */ private float mTextWidth; /** * 在java代碼中創(chuàng)建view的時候調(diào)用,即new * @param context */ public ValidationCode(Context context) { this(context,null); } /** * 在xml布局文件中使用view但沒有指定style的時候調(diào)用 * @param context * @param attrs */ public ValidationCode(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * 在xml布局文件中使用view并指定style的時候調(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); // 獲取布局中驗證碼位數(shù)屬性值,默認為5個 // 獲取布局中驗證碼文字的大小,默認為20sp mTextSize = typedArray.getDimension(R.styleable.IndentifyingCode_textSize, typedArray.getDimensionPixelSize(R.styleable.IndentifyingCode_textSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()))); // 一個好的習(xí)慣是用完資源要記得回收,就想打開數(shù)據(jù)庫和IO流用完后要記得關(guān)閉一樣 typedArray.recycle(); } /** * 要像layout_width和layout_height屬性支持wrap_content就必須重新這個方法 * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 分別測量控件的寬度和高度,基本為模板方法 int measureWidth = measureWidth(widthMeasureSpec); int measureHeight = measureHeight(heightMeasureSpec); // 其實這個方法最終會調(diào)用setMeasuredDimension(int measureWidth,int measureHeight); // 將測量出來的寬高設(shè)置進去完成測量 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); // 這里只會產(chǎn)生0和1,如果是1那么正旋轉(zhuǎn)正角度,否則旋轉(zhuǎn)負角度 offsetDegree = mRandom.nextInt(2) == 1?offsetDegree:-offsetDegree; canvas.save(); 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(mCodeString.charAt(i - 1)), (i-1) * charLength * 1.6f+30, mHeight * 2 / 3f, mTextPaint); canvas.restore(); } // 產(chǎn)生干擾效果1 -- 干擾點 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 -- 干擾線 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() { // 獲取控件的寬和高,此時已經(jīng)測量完成 mHeight = getHeight(); mWidth = getWidth(); 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); } } /** * 初始化一些數(shù)據(jù) */ private void init() { // 生成隨機數(shù)字和字母組合 mCodeString = getCharAndNumr(mCodeCount); // 初始化文字畫筆 mTextPaint = new Paint(); mTextPaint.setStrokeWidth(3); // 畫筆大小為3 mTextPaint.setTextSize(mTextSize); // 設(shè)置文字大小 // 初始化干擾點畫筆 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(mCodeString); } /** * java生成隨機數(shù)字和字母組合 * @param length[生成隨機數(shù)的長度] * @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)) { // 取得大寫字母還是小寫字母 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: // 重新生成隨機數(shù)字和字母組合 mCodeString = getCharAndNumr(mCodeCount); invalidate(); break; default: break; } return super.onTouchEvent(event); } /** * 測量寬度 * @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){ // 精確測量模式,即布局文件中l(wèi)ayout_width或layout_height一般為精確的值或match_parent result = widthSize; // 既然是精確模式,那么直接返回測量的寬度即可 }else{ if(widthMode == MeasureSpec.AT_MOST) { // 最大值模式,即布局文件中l(wèi)ayout_width或layout_height一般為wrap_content result = Math.min(result,widthSize); } } return result; } /** * 測量高度 * @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){ // 精確測量模式,即布局文件中l(wèi)ayout_width或layout_height一般為精確的值或match_parent result = heightSize; // 既然是精確模式,那么直接返回測量的寬度即可 }else{ if(heightMode == MeasureSpec.AT_MOST) { // 最大值模式,即布局文件中l(wèi)ayout_width或layout_height一般為wrap_content result = Math.min(result,heightSize); } } return result; } /** * 獲取驗證碼字符串,進行匹配的時候只需要字符串比較即可(具體比較規(guī)則自己決定) * @return 驗證碼字符串 */ public String getCodeString() { return mCodeString; } }
總結(jié):這里與其說自定義View到不如說是繪制圖形,關(guān)鍵在于坐標(biāo)點的計算,這里在計算坐標(biāo)上也許不太好,以上是給大家分享Android自定義view制作絢麗的驗證碼,希望對大家有所幫助!大家有什么好的思路或者建議希望可以留言告訴我,感激不盡~。
相關(guān)文章
Android實現(xiàn)五子棋游戲(局域網(wǎng)版)
這篇文章主要為大家詳細介紹了Android實現(xiàn)局域網(wǎng)版的五子棋游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05Android 監(jiān)聽Notification 被清除實例代碼
本文主要介紹Android 監(jiān)聽Notification 事件,這里給大家提供實例代碼進行參考,有需要的小伙伴可以參考下2016-07-07android實現(xiàn)主動連接和被動連接的藍牙聊天功能
這篇文章主要為大家詳細介紹了android實現(xiàn)主動連接和被動連接的藍牙聊天功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-06-06利用源碼編譯Android系統(tǒng)的APK和可執(zhí)行命令的方法
這篇文章主要介紹了利用源碼編譯Android系統(tǒng)的APK和可執(zhí)行命令的方法,示例在Linux系統(tǒng)環(huán)境上進行構(gòu)建,需要的朋友可以參考下2016-02-02Android實現(xiàn)EditText內(nèi)容保存為Bitmap的方法
這篇文章主要介紹了Android實現(xiàn)EditText內(nèi)容保存為Bitmap的方法,涉及Android中saveBitmap方法的簡單使用技巧,需要的朋友可以參考下2016-01-01Android常用三方庫混淆規(guī)則整理(小結(jié))
這篇文章主要介紹了Android常用三方庫混淆規(guī)則整理(小結(jié)),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07