2018-09-26【自定義圖片驗證碼】

Android自定義View之隨機(jī)生成圖片驗證碼,開發(fā)中我們會經(jīng)常需要隨機(jī)生成圖片驗證碼,但是這個是其次,主要還是想總結(jié)一些自定義View的開發(fā)過程以及一些需要注意的地方。

一、先總結(jié)下自定義View的步驟:?

1、自定義View的屬性?

2、在View的構(gòu)造方法中獲得我們自定義的屬性?

3、重寫onMesure?

4、重寫onDraw?

其中onMesure方法不一定要重寫,但大部分情況下還是需要重寫的

二、View 的幾個構(gòu)造函數(shù)?

1、public CustomView(Context context)?

—>java代碼直接new一個CustomView實例的時候,會調(diào)用這個只有一個參數(shù)的構(gòu)造函數(shù);?

2、public CustomView(Context context, AttributeSet attrs)?

—>在默認(rèn)的XML布局文件中創(chuàng)建的時候調(diào)用這個有兩個參數(shù)的構(gòu)造函數(shù)。AttributeSet類型的參數(shù)負(fù)責(zé)把XML布局文件中所自定義的屬性通過AttributeSet帶入到View內(nèi);?

3、public CustomView(Context context, AttributeSet attrs, int defStyleAttr)?

—>構(gòu)造函數(shù)中第三個參數(shù)是默認(rèn)的Style,這里的默認(rèn)的Style是指它在當(dāng)前Application或者Activity所用的Theme中的默認(rèn)Style,且只有在明確調(diào)用的時候才會調(diào)用?

4、public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)?

—>該構(gòu)造函數(shù)是在API21的時候才添加上的

三、下面我們就開始來看看代碼啦?

1、自定義View的屬性,首先在res/values/ 下建立一個attr.xml , 在里面定義我們的需要用到的屬性以及聲明相對應(yīng)屬性的取值類型

<?xml version="1.0" encoding="utf-8"?>

<resources>

<attr name="text" format="string" />

<attr name="textColor" format="color" />

<attr name="textSize" format="dimension" />

<attr name="bgColor" format="color" />

<declare-styleable name="CustomView">

? <attr name="text" />

? <attr name="textColor" />

? <attr name="textSize" />

? <attr name="bgColor" />

</declare-styleable>

</resources>

我們定義了字體,字體顏色,字體大小以及字體的背景顏色4個屬性,format是值該屬性的取值類型,format取值類型總共有10種,包括:string,color,demension,integer,enum,reference,float,boolean,fraction和flag。

2、然后在XML布局中聲明我們的自定義View

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:custom="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"

android:layout_height="match_parent">

<com.per.customview01.view.CustomView

? android:layout_width="wrap_content"

? android:layout_height="wrap_content"

? android:layout_centerInParent="true"

? android:padding="10dp"

? custom:bgColor="#FF27FF28"

? custom:text="J2RdWQG"

? custom:textColor="#ff0000"

? custom:textSize="36dp" />

</RelativeLayout>

一定要引入xmlns:custom=”http://schemas.android.com/apk/res-auto”,Android Studio中我們可以使用res-atuo命名空間,就不用在添加自定義View全類名。

3、在View的構(gòu)造方法中,獲得我們的自定義的樣式

/**

? * 文本

? */

private String mText;

/**

? * 文本的顏色

? */

private int mTextColor;

/**

? * 文本的大小

? */

private int mTextSize;

/**

? * 文本的背景顏色

? */

private int mBgCplor;

private Rect mBound;

private Paint mPaint;

public CustomView(Context context) {

? this(context, null);

}

public CustomView(Context context, AttributeSet attrs) {

? this(context, attrs, 0);

}

public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {

? super(context, attrs, defStyleAttr);

? /**

? * 獲得我們所定義的自定義樣式屬性

? */

? TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, defStyleAttr, 0);

? for (int i = 0; i < a.getIndexCount(); i++) {

? int attr = a.getIndex(i);

? switch (attr) {

? ? case R.styleable.CustomView_text:

? ? mText = a.getString(attr);

? ? break;

? ? case R.styleable.CustomView_textColor:

? ? // 默認(rèn)文本顏色設(shè)置為黑色

? ? mTextColor = a.getColor(R.styleable.CustomView_textColor, Color.BLACK);

? ? break;

? ? case R.styleable.CustomView_bgColor:

? ? // 默認(rèn)文本背景顏色設(shè)置為藍(lán)色

? ? mBgCplor = a.getColor(R.styleable.CustomView_bgColor, Color.BLUE);

? ? break;

? ? case R.styleable.CustomView_textSize:

? ? // 默認(rèn)設(shè)置為16sp,TypeValue也可以把sp轉(zhuǎn)化為px

? ? mTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));

? ? break;

? }

? }

? a.recycle();

? // 獲得繪制文本的寬和高

? mPaint = new Paint();

? mPaint.setTextSize(mTextSize);

? mBound = new Rect();

? mPaint.getTextBounds(mText, 0, mText.length(), mBound);

}

我們重寫了3個構(gòu)造方法,在上面的構(gòu)造方法中說過默認(rèn)的布局文件調(diào)用的是兩個參數(shù)的構(gòu)造方法,所以記得讓所有的構(gòu)造方法調(diào)用三個參數(shù)的構(gòu)造方法,然后在三個參數(shù)的構(gòu)造方法中獲得自定義屬性。?

一開始一個參數(shù)的構(gòu)造方法和兩個參數(shù)的構(gòu)造方法是這樣的:

public CustomView(Context context) {

? super(context);

}

public CustomView(Context context, AttributeSet attrs) {

? super(context, attrs);

}

有一點要注意的是super應(yīng)該改成this,然后讓一個參數(shù)的構(gòu)造方法引用兩個參數(shù)的構(gòu)造方法,兩個參數(shù)的構(gòu)造方法引用三個參數(shù)的構(gòu)造方法,代碼如下:

public CustomView(Context context) {

? this(context, null);

}

public CustomView(Context context, AttributeSet attrs) {

? this(context, attrs, 0);

}

4、重寫onDraw,onMesure方法

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

? super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

@Override

protected void onDraw(Canvas canvas) {

? super.onDraw(canvas);

? mPaint.setColor(mBgCplor);

? canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

? mPaint.setColor(mTextColor);

? canvas.drawText(mText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);

}

View的繪制流程是從ViewRoot的performTravarsals方法開始的,經(jīng)過measure、layout和draw三個過程才能最終將一個View繪制出來,其中:

?測量——onMeasure():用來測量View的寬和高來決定View的大小

?布局——onLayout():用來確定View在父容器ViewGroup中的放置位置

?繪制——onDraw():負(fù)責(zé)將View繪制在屏幕上?

來看下此時的效果圖

細(xì)心的朋友會發(fā)現(xiàn),在上面的布局文件中,我們是把寬和高設(shè)置為wrap_content的,可是這個效果圖怎么看都不是我們想要的,這是因為系統(tǒng)幫我們測量的高度和寬度默認(rèn)是MATCH_PARNET,當(dāng)我們設(shè)置明確的寬度和高度時,系統(tǒng)幫我們測量的結(jié)果就是我們設(shè)置的結(jié)果,這個是對的。但是除了設(shè)置明確的寬度和高度,不管我們設(shè)置為WRAP_CONTENT還是MATCH_PARENT,系統(tǒng)幫我們測量的結(jié)果就是MATCH_PARENT,所以,當(dāng)我們設(shè)置了WRAP_CONTENT時,我們需要自己進(jìn)行測量,也就是說我們需要重寫onMesure方法

下面是我們重寫onMeasure代碼:

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

? super.onMeasure(widthMeasureSpec, heightMeasureSpec);

? int widthMode = MeasureSpec.getMode(widthMeasureSpec);

? int widthSize = MeasureSpec.getSize(widthMeasureSpec);

? int heighMode = MeasureSpec.getMode(heightMeasureSpec);

? int heighSize = MeasureSpec.getSize(heightMeasureSpec);

? setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : getPaddingLeft() + getPaddingRight() + mBound.width(), heighMode == MeasureSpec.EXACTLY ? heighSize : getPaddingTop() + getPaddingBottom() + mBound.height());

}

MeasureSpec封裝了父布局傳遞給子布局的布局要求,MeasureSpec的specMode一共有三種模式:?

(1)EXACTLY(完全):一般是設(shè)置了明確的值或者是MATCH_PARENT,父元素決定了子元素的大小,子元素將被限定在給定的范圍里而忽略它本身大?。?

(2)AT_MOST(至多):表示子元素至多達(dá)到給定的一個最大值,一般為WARP_CONTENT;

現(xiàn)在這個是我們想要的結(jié)果了吧,回歸到主題,今天講的是自定義View之隨機(jī)生成圖片驗證碼,現(xiàn)在把自定義View的部分完成了,我把完整的代碼貼出來

package com.per.customview01.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.util.AttributeSet;

import android.util.Log;

import android.util.TypedValue;

import android.view.View;

import com.per.customview01.R;

import java.util.Random;

/**

* @author: adan

* @description:

* @projectName: CustomView01

* @date: 2016-06-12

* @time: 10:26

*/

public class CustomView extends View {

private static final char[] 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'};

/**

? * 初始化生成隨機(jī)數(shù)的類

? */

private Random mRandom = new Random();

/**

? * 初始化可變字符串

? */

private StringBuffer sb = new StringBuffer();

/**

? * 文本

? */

private String mText;

/**

? * 文本的顏色

? */

private int mTextColor;

/**

? * 文本的大小

? */

private int mTextSize;

/**

? * 文本的背景顏色

? */

private int mBgCplor;

private Rect mBound;

private Paint mPaint;

public CustomView(Context context) {

? this(context, null);

}

public CustomView(Context context, AttributeSet attrs) {

? this(context, attrs, 0);

}

public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {

? super(context, attrs, defStyleAttr);

? /**

? * 獲得我們所定義的自定義樣式屬性

? */

? TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, defStyleAttr, 0);

? for (int i = 0; i < a.getIndexCount(); i++) {

? int attr = a.getIndex(i);

? switch (attr) {

? ? case R.styleable.CustomView_text:

? ? mText = a.getString(attr);

? ? break;

? ? case R.styleable.CustomView_textColor:

? ? // 默認(rèn)文本顏色設(shè)置為黑色

? ? mTextColor = a.getColor(R.styleable.CustomView_textColor, Color.BLACK);

? ? break;

? ? case R.styleable.CustomView_bgColor:

? ? // 默認(rèn)文本背景顏色設(shè)置為藍(lán)色

? ? mBgCplor = a.getColor(R.styleable.CustomView_bgColor, Color.BLUE);

? ? break;

? ? case R.styleable.CustomView_textSize:

? ? // 默認(rèn)設(shè)置為16sp,TypeValue也可以把sp轉(zhuǎn)化為px

? ? mTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));

? ? break;

? }

? }

? a.recycle();

? // 獲得繪制文本的寬和高

? mPaint = new Paint();

? mPaint.setTextSize(mTextSize);

? mBound = new Rect();

? mPaint.getTextBounds(mText, 0, mText.length(), mBound);

? this.setOnClickListener(new OnClickListener() {

? @Override

? public void onClick(View v) {

? ? mText = createCode();

? ? mTextColor = randomColor();

? ? mBgCplor = randomColor();

? ? //View重新調(diào)用一次draw過程,以起到界面刷新的作用

? ? postInvalidate();

? }

? });

}

/**

? * 生成驗證碼

? */

public String createCode() {

? sb.delete(0, sb.length()); // 使用之前首先清空內(nèi)容

? for (int i = 0; i < 6; i++) {

? sb.append(CHARS[mRandom.nextInt(CHARS.length)]);

? }

? Log.e("生成驗證碼", sb.toString());

? return sb.toString();

}

/**

? * 隨機(jī)顏色

? */

private int randomColor() {

? sb.delete(0, sb.length()); // 使用之前首先清空內(nèi)容

? String haxString;

? for (int i = 0; i < 3; i++) {

? haxString = Integer.toHexString(mRandom.nextInt(0xFF));

? if (haxString.length() == 1) {

? ? haxString = "0" + haxString;

? }

? sb.append(haxString);

? }

? Log.e("隨機(jī)顏色", "#" + sb.toString());

? return Color.parseColor("#" + sb.toString());

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

? super.onMeasure(widthMeasureSpec, heightMeasureSpec);

? int widthMode = MeasureSpec.getMode(widthMeasureSpec);

? int widthSize = MeasureSpec.getSize(widthMeasureSpec);

? int heighMode = MeasureSpec.getMode(heightMeasureSpec);

? int heighSize = MeasureSpec.getSize(heightMeasureSpec);

? setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : getPaddingLeft() + getPaddingRight() + mBound.width(), heighMode == MeasureSpec.EXACTLY ? heighSize : getPaddingTop() + getPaddingBottom() + mBound.height());

}

@Override

protected void onDraw(Canvas canvas) {

? super.onDraw(canvas);

? mPaint.setColor(mBgCplor);

? canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

? mPaint.setColor(mTextColor);

? canvas.drawText(mText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);

}

}

我們添加了一個點擊事件,每一次點擊View我都讓它把生成的驗證碼和字體顏色以及字體背景顏色打印出來

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容