Android自定義View實現(xiàn)驗證碼
本文章是基于鴻洋的Android 自定義View (一) 的一些擴(kuò)展,以及對Android自定義View構(gòu)造函數(shù)詳解里面內(nèi)容的一些轉(zhuǎn)載。
首先我們定義一個declare-styleable標(biāo)簽declare-styleable標(biāo)簽的作用是給自定義控件添加自定義屬性用的例如這樣
(我們定義了文字的顏色,大小,長度,跟背景的顏色)
<declare-styleable name="CustomTitleView"> <attr name="titleColor" format="color" /> <attr name="titleSize" format="dimension" /> <attr name="titleBackground" format="color" /> <attr name="titleLenth" format="integer" /> </declare-styleable>
Android提供了自定義屬性的方法,其中的format的參數(shù)有
(reference、color、boolean、dimension、float、integer、string、fraction、enum、flag)
1.reference:資源ID:
如果設(shè)置了這個屬性那么這個屬性相當(dāng)于@string|@drawable等調(diào)用資源文件的作用
2. color:
這個屬性的作用為設(shè)置顏色值8或者6位的16進(jìn)制的顏色值,如設(shè)置TextView的textColor等屬性的作用相同(如#ff000設(shè)置為紅色等)
3.boolean:
這個參數(shù)的作用為設(shè)置true或者false
4.dimension:
這個參數(shù)的作用為設(shè)置尺寸值,如px、dip、dp、sp等
5.float:
這個參數(shù)的作用為設(shè)置浮點型數(shù)據(jù)
6.integer:
這個參數(shù)的作用為設(shè)置整形數(shù)據(jù)
7.string:
這個參數(shù)的作用為設(shè)置字符串?dāng)?shù)據(jù),如TextView的text屬性
8.fraction:
這個參數(shù)的作用為設(shè)置百分比數(shù)據(jù)
9:enum:
這個參數(shù)相當(dāng)于給這個attr的name屬性設(shè)置固定的參數(shù),如線性布局的orientation屬性只能設(shè)置vertical或者h(yuǎn)orizontal
10:flag:
這個參數(shù)作用為:位或運(yùn)算
一個自定義View的步驟為
1、自定義View的屬性
2、在View的構(gòu)造方法中獲得我們自定義的屬性
3、重寫onMeasure
4、重寫onDraw
有的時候onMeasure方法是不用重寫的例如系統(tǒng)自帶組件等
然后我們定義一下需要的屬性
//文本
private StringBuffer mTitleText;
//文本的顏色
private int mTitleColor;
//文本的大小
private int mTitleSize;
//背景顏色
private int mBackground;
//控制生成的隨機(jī)字符串長度
private int mLenth;
//繪制時控制文本繪制的范圍
private Rect mBound;
//畫筆
private Paint mPaint;
//隨機(jī)數(shù)對象
private Random random = new Random();
//字符串邊距
private int padding_left;
//隨機(jī)的值
String[] data = {"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",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
然后我們重寫三個構(gòu)造方法,我們需要注意的是
1、在代碼中直接new一個自定義View實例的時候,會調(diào)用第一個構(gòu)造函數(shù).
2、在xml布局文件中調(diào)用自定義View的時候,會調(diào)用第二個構(gòu)造函數(shù).
3、在xml布局文件中調(diào)用自定義View,并且自定義標(biāo)簽中還有自定義屬性時,這里調(diào)用的還是第二個構(gòu)造函數(shù).
也就是說,系統(tǒng)默認(rèn)只會調(diào)用Custom View的前兩個構(gòu)造函數(shù),至于第三個構(gòu)造函數(shù)的調(diào)用,通常是我們自己在構(gòu)造函數(shù)中主動調(diào)用的(例如,在第二個構(gòu)造函數(shù)中調(diào)用第三個構(gòu)造函數(shù)).
至于自定義屬性的獲取,通常是在構(gòu)造函數(shù)中通過obtainStyledAttributes函數(shù)實現(xiàn)的。
public CustomTitleView(Context context) {
this(context, null);
}
public CustomTitleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomTitleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnClickListener(this);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomTitleView);
int n = typedArray.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.CustomTitleView_titleColor:
mTitleColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.CustomTitleView_titleSize:
mTitleSize = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
case R.styleable.CustomTitleView_titleBackground:
mBackground = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.CustomTitleView_titleLenth:
mLenth = typedArray.getInteger(attr, 4);
break;
}
}
//回收
typedArray.recycle();
mPaint = new Paint();
randomText();
mPaint.setTextSize(mTitleSize);
//創(chuàng)建一個矩形
mBound = new Rect();
//第一個參數(shù)為要測量的文字,第二個參數(shù)為測量起始位置,第三個參數(shù)為測量的最后一個字符串的位置,第四個參數(shù)為rect對象
mPaint.getTextBounds(mTitleText.toString(), 0, mTitleText.length(), mBound);
}
obtainStyledAttributes的第二個屬性為調(diào)用你剛在在attrs.xml文件里生命的declare-styleable標(biāo)簽的name
然后我們重寫一下onMeasure方法,通過getMeasuredLength方法計算出寬和高
/**
* 計算寬高
*
* @param lenth widthMeasureSpec或heightMeasureSpec
* @param isWidth true為計算寬度,false為計算高度
*/
private int getMeasuredLength(int lenth, boolean isWidth) {
if (isWidth) {
if (MeasureSpec.getMode(lenth) == MeasureSpec.EXACTLY) {
//設(shè)置了精確尺寸,通過MeasureSpec.getSize()獲得尺寸返回寬度
return MeasureSpec.getSize(lenth);
} else {
//設(shè)置了warp_content,則需要我們自己計算
/**
* 首先給畫筆設(shè)置文字大小
* 通過getTextBounds方法獲得繪制的Text的寬度
* 然后因為我們的自定義View只有一個text所以我們只需要getPaddingLeft()+getPaddingRight()+textwidth即可計算出顯示出view所需要最小的寬度
* 一般計算寬度為getPaddingLeft()+getPaddingRight()+自己繪畫的文字或者圖片的寬度,因為計算的是所需寬度,假設(shè)我們繪制了圖片+文字,那么就需要判斷圖片的寬度跟文字的寬度那個更大比如getPaddingLeft()+getPaddingRight()+Math.max(圖片的寬度,文字的寬度)即得出所需寬度
*/
if (MeasureSpec.getMode(lenth) == MeasureSpec.AT_MOST) {
mPaint.setTextSize(mTitleSize);
mPaint.getTextBounds(mTitleText.toString(), 0, mTitleText.length(), mBound);
float textwidth = mBound.width();
int desired = (int) (getPaddingLeft() + textwidth + getPaddingRight());
return Math.min(desired,MeasureSpec.getSize(lenth));
}
}
} else {
if (MeasureSpec.getMode(lenth) == MeasureSpec.EXACTLY) {
//用戶設(shè)置了精確尺寸,通過MeasureSpec.getSize()獲得尺寸返回高度
return MeasureSpec.getSize(lenth);
} else {
if (MeasureSpec.getMode(lenth) == MeasureSpec.AT_MOST) {
//設(shè)置了warp_content,則需要我們自己計算
mPaint.setTextSize(mTitleSize);
mPaint.getTextBounds(mTitleText.toString(), 0, mTitleText.length(), mBound);
float texthgeight = mBound.height();
int desired = (int) (getPaddingTop() + texthgeight + getPaddingBottom());
return Math.min(desired,MeasureSpec.getSize(lenth));
}
}
}
return 0;
}
然后在onMeasure方法里調(diào)用
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getMeasuredLength(widthMeasureSpec, true), getMeasuredLength(heightMeasureSpec, false));
}
系統(tǒng)幫我們測量的高度和寬度都是MATCH_PARNET,當(dāng)我們設(shè)置明確的寬度和高度時,系統(tǒng)幫我們測量的結(jié)果就是我們設(shè)置的結(jié)果,當(dāng)我們設(shè)置為WRAP_CONTENT,或者M(jìn)ATCH_PARENT系統(tǒng)幫我們測量的結(jié)果就是MATCH_PARENT的長度。
所以,當(dāng)設(shè)置了WRAP_CONTENT時,我們需要自己進(jìn)行測量,即重寫onMeasure方法
重寫之前先了解MeasureSpec的specMode,一共三種類型:
EXACTLY:一般是設(shè)置了明確的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一個最大值內(nèi),一般為WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
在這里有些初學(xué)者可能不理解getMode跟getSize的作用,首先getMode()用于判斷寬高設(shè)置的模式,獲得到之后即可判斷,例如
//xx表示widthMeasureSpec或者h(yuǎn)eightMeasureSpec
if(MeasureSpec.getMode(xx)==MeasureSpec.EXACTLY){
//進(jìn)入這里則代表設(shè)置了match_parent或者將控件的layout_width或layout_height指定為具體數(shù)值時如andorid:layout_width="100dp",這樣我們就可以直接通過MeasureSpec.getSize(xx)方法獲得寬或高
}else if(MeasureSpec.getMode(xx)==MeasureSpec.EXACTLY){
//進(jìn)入這里代表設(shè)置了wrap_content,那么則需要我們自己計算寬或高
}else{
//進(jìn)入這個則代表代表是未指定尺寸,這種情況不多,一般都是父控件是AdapterView,通過measure方法傳入的模式
}
然后我們重寫一下onDraw方法、
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
padding_left = 0;
mPaint.setColor(mBackground);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
mPaint.setColor(mTitleColor);
for (int i = 0; i < mTitleText.length(); i++) {
randomTextStyle(mPaint);
padding_left += mPaint.measureText(String.valueOf(mTitleText.charAt(i)))+10;
canvas.drawText(String.valueOf(mTitleText.charAt(i)), padding_left, getHeight() / 2 + mBound.height() / 2, mPaint);
}
}
private void randomTextStyle(Paint paint) {
paint.setFakeBoldText(random.nextBoolean()); //true為粗體,false為非粗體
float skewX = random.nextInt(11) / 10;
skewX = random.nextBoolean() ? skewX : -skewX;
paint.setTextSkewX(skewX); //float類型參數(shù),負(fù)數(shù)表示右斜,整數(shù)左斜
paint.setUnderlineText(true); //true為下劃線,false為非下劃線
paint.setStrikeThruText(false); //true為刪除線,false為非刪除線
}
這里繪制了多個字符串,并且使每個繪制的字符串都歪歪扭扭的,這樣我們采用randomTextStyle()即可在每次繪制字符的時候設(shè)置每個字符都為不同的樣式,在這里我們講一下drawText的幾個參數(shù),第一個參數(shù)就是要繪制的文字內(nèi)容,第二個參數(shù)為x軸,作用相當(dāng)于左邊距,第三個參數(shù)為Y軸,第四個參數(shù)為paint的實例,我的朋友具體講了一下drawText的繪制坐標(biāo)有興趣的可以去看一下android canvas drawText()文字居中
最后我們在布局文件中引用我們的自定義view
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cq="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <chapter.com.rxjavachapter.CustomTitleView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:padding="10dp" cq:titleBackground="@android:color/black" cq:titleColor="#ff0000" cq:titleLenth="4" cq:titleSize="10sp" /> </RelativeLayout>
在根布局添加 xmlns:xx=”http://schemas.android.com/apk/res-auto” 這里的xx可以是任何字母
然后用xx去點我們在attr的name去設(shè)值,最后實現(xiàn)出的效果是這樣

既然是驗證碼view,那么我們自然要開放出點擊改變驗證碼內(nèi)容的點擊事件,在第三個構(gòu)造方法中添加click事件
@Override
public void onClick(View v) {
randomText();
postInvalidate();
}
/**
* 獲得隨機(jī)的字符串
*
* @return
*/
private void randomText() {
mTitleText = new StringBuffer();
for (int i = 0; i < mLenth; i++) {
mTitleText.append(data[(int) (Math.random() * data.length)]);
}
}
/**
* 獲得到隨機(jī)的值
*
* @return
*/
public String getCode() {
return mTitleText.toString();
}
/**
* 判斷是否相同
*
* @return
*/
public boolean isEqual(String code) {
if (code != null) {
return code.toUpperCase().equals(getCode().toUpperCase()) ? true : false;
} else {
return false;
}
}
這樣就可以點擊改變一次驗證碼內(nèi)容了,并且我們開放出兩個方法作為判斷驗證碼或得到驗證碼,我這只是簡單的一個驗證碼,大家可以自己加入更多的東西。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android layoutAnimation詳解及應(yīng)用
這篇文章主要介紹了Android layoutAnimation詳解及應(yīng)用的相關(guān)資料,需要的朋友可以參考下2017-05-05
Android穩(wěn)定性:可遠(yuǎn)程配置化的Looper兜底框架
這篇文章主要為大家介紹了Android穩(wěn)定性可遠(yuǎn)程配置化的Looper兜底框架實例實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
Android使用FontMetrics對象計算位置坐標(biāo)
這篇文章主要為大家詳細(xì)介紹了Android使用FontMetrics對象計算位置坐標(biāo),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-12-12
Android 一鍵清理、內(nèi)存清理功能實現(xiàn)
這篇文章主要介紹了Android 一鍵清理、內(nèi)存清理功能實現(xiàn),非常具有實用價值,需要的朋友可以參考下。2017-01-01
Kotlin基礎(chǔ)學(xué)習(xí)之循環(huán)和異常
最近在學(xué)習(xí)kotlin,Kotlin 是一個基于 JVM 的新的編程語言,下面這篇文章主要給大家介紹了關(guān)于Kotlin基礎(chǔ)學(xué)習(xí)之循環(huán)和異常的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-12-12
Android報錯Didn‘t?find?class?“android.view.x“問題解決原理剖析
這篇文章主要為大家介紹了Android報錯Didn‘t?find?class?“android.view.x“問題解決及原理剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03

