這是一個仿照探探的驗證碼輸入控件,防止輸入溢出,支持自身屬性自定義等.
該項目以上傳至Github中,歡迎star/fork,項目地址VercideEditText
一、 項目說明
1.1 demo演示

1.2 特性
- [x] 繼承EditText,可以當(dāng)作EditText使用
- [x] 防止輸入溢出
- [x] 自定義驗證碼位數(shù)
- [x] 提供輸入內(nèi)容的監(jiān)聽器
- [x] 高度自適配
- [x] 屬性自定義配置
1.3 集成
JCenter方式
第一步,添加至工程的build.gradle文件中
repositories {
jcenter()
}
第二步,添加至module的build.gradle文件中
compile 'com.justkiddingbaby:vercodeedittext:1.0.0'
1.4 屬性說明
| 屬性 | 介紹 | 取值 |
|---|---|---|
| figures | 驗證碼位數(shù) | integer |
| verCodeMargin | 每個驗證碼的間隔 | dimension |
| bottomLineSelectedColor | 底線選擇狀態(tài)下的顏色 | reference |
| bottomLineNormalColor | 底線未選中狀態(tài)下的顏色 | reference |
| bottomLineHeight | 底線高度 | dimension |
| selectedBackgroundColor | 選中的背景顏色 | reference |
1.5 使用
在布局中使用
<com.jkb.vcedittext.VerificationCodeEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:text="123"
android:textColor="@color/colorPrimary"
android:textSize="40sp"
app:bottomLineHeight="2dp"
app:bottomLineNormalColor="@color/gravy_light"
app:bottomLineSelectedColor="@color/colorAccent"
app:figures="4"
app:selectedBackgroundColor="@color/colorPrimary_alpha33"
app:verCodeMargin="10dp" />
二、 代碼分析
下面讓我們來一步一步對該項目的源碼進行分析.
2.1 需求分析
這是一個驗證碼輸入控件,根據(jù)demo演示分析得出,需要支持的屬性有:
1、作為EditText控件使用(繼承EditText)
2、自定義驗證碼輸入位數(shù),并且支持每位驗證碼的margin自定義
3、有背景的選擇變化和底部下劃線的輸入變化,并且支持顏色自定義
4、高度自適配,wrap_content時高度和每位驗證碼寬度一樣
5、支持輸入內(nèi)容變化的監(jiān)聽
2.2 聲明接口和屬性
本節(jié)將對2.1中的需求進行分析,并定義出屬于該控件獨有的屬性。
2.2.1 屬性聲明
在values目錄下創(chuàng)建attrs.xml文件(如果無則創(chuàng)建),這是為了支持在xml中直接配置控件的相關(guān)屬性。
<declare-styleable name="VerCodeEditText">
<!--驗證碼位數(shù)-->
<attr name="figures" format="integer" />
<!--驗證碼每位之間間隔-->
<attr name="verCodeMargin" format="dimension" />
<!--底線選中狀態(tài)下的顏色-->
<attr name="bottomLineSelectedColor" format="reference" />
<!--底線未選中狀態(tài)下的顏色-->
<attr name="bottomLineNormalColor" format="reference" />
<!--底線高度-->
<attr name="bottomLineHeight" format="dimension" />
<!--驗證碼選中狀態(tài)下的背景顏色-->
<attr name="selectedBackgroundColor" format="reference" />
</declare-styleable>
2.2.2 接口聲明
新建一個接口類VerificationAction,(這是為了支持使用Java代碼配置控件的屬性)。
interface VerificationAction {
/**
* 設(shè)置位數(shù)
*/
void setFigures(int figures);
/**
* 設(shè)置驗證碼之間的間距
*/
void setVerCodeMargin(int margin);
/**
* 設(shè)置底部選中狀態(tài)的顏色
*/
void setBottomSelectedColor(@ColorRes int bottomSelectedColor);
/**
* 設(shè)置底部未選中狀態(tài)的顏色
*/
void setBottomNormalColor(@ColorRes int bottomNormalColor);
/**
* 設(shè)置選擇的背景色
*/
void setSelectedBackgroundColor(@ColorRes int selectedBackground);
/**
* 設(shè)置底線的高度
*/
void setBottomLineHeight(int bottomLineHeight);
/**
* 設(shè)置當(dāng)驗證碼變化時候的監(jiān)聽器
*/
void setOnVerificationCodeChangedListener(OnVerificationCodeChangedListener listener);
/**
* 驗證碼變化時候的監(jiān)聽事件
*/
interface OnVerificationCodeChangedListener {
/**
* 當(dāng)驗證碼變化的時候
*/
void onVerCodeChanged(CharSequence s, int start, int before, int count);
/**
* 輸入完畢后的回調(diào)
*/
void onInputCompleted(CharSequence s);
}
}
根據(jù)上述接口和屬性的定義內(nèi)容可以看出,接口的內(nèi)容和完全和屬性向?qū)?yīng),6個屬性對應(yīng)6個set方法,另外接口中單獨定義了驗證碼輸入內(nèi)容變化的監(jiān)聽接口。
2.3 控件自定義
接下來我們就一步步地完成這個控件。
2.3.1 創(chuàng)建類,并聲明相關(guān)屬性并繼承EditText和實現(xiàn)屬性接口VerificationAction
public class VerificationCodeEditText extends android.support.v7.widget.AppCompatEditText implements VerificationAction{
//attrs
private int mFigures;//需要輸入的位數(shù)
private int mVerCodeMargin;//驗證碼之間的間距
private int mBottomSelectedColor;//底部選中的顏色
private int mBottomNormalColor;//未選中的顏色
private float mBottomLineHeight;//底線的高度
private int mSelectedBackgroundColor;//選中的背景顏色
public VerificationCodeEditText(Context context) {
this(context, null);
}
public VerificationCodeEditText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VerificationCodeEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(attrs);
}
/**
* 初始化屬性
*/
private void initAttrs(AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.VerCodeEditText);
mFigures = ta.getInteger(R.styleable.VerCodeEditText_figures, 4);
mVerCodeMargin = (int) ta.getDimension(R.styleable.VerCodeEditText_verCodeMargin, 0);
mBottomSelectedColor = ta.getColor(R.styleable.VerCodeEditText_bottomLineSelectedColor,
getCurrentTextColor());
mBottomNormalColor = ta.getColor(R.styleable.VerCodeEditText_bottomLineNormalColor,
getColor(android.R.color.darker_gray));
mBottomLineHeight = ta.getDimension(R.styleable.VerCodeEditText_bottomLineHeight,
dp2px(5));
mSelectedBackgroundColor = ta.getColor(R.styleable.VerCodeEditText_selectedBackgroundColor,
getColor(android.R.color.darker_gray));
ta.recycle();
}
...set方法
}
上述代碼沒啥好說的,只是定義了類的基礎(chǔ)機構(gòu),關(guān)于View的三個構(gòu)造方法的區(qū)別,可以參考文章 Android自定義View構(gòu)造函數(shù)詳解
2.3.2 測量寬高
測量View的寬高需要重寫onMeasure(int,int)方法,因為本控件是繼承EditText的,而EditText默認(rèn)的測量和本需求不同,所以需要完全自己實現(xiàn)該方法,點開super.onMeasure(int,int)方法后,發(fā)現(xiàn)最終調(diào)用的方法是setMeasuredDimension(int, int),該方法的代碼如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthResult = 0, heightResult = 0;
//最終的寬度
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
widthResult = widthSize;
} else {
widthResult = getScreenWidth(getContext());//默認(rèn)為屏幕的寬度
}
//每位驗證碼的寬度
mEachRectLength = (widthResult - (mVerCodeMargin * (mFigures - 1))) / mFigures;
//最終的高度
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
heightResult = heightSize;
} else {
heightResult = mEachRectLength;
}
setMeasuredDimension(widthResult, heightResult);
}
該控件的寬高測量比較簡單,相信上述代碼還是比較明了的,在此就不再做相關(guān)解釋了。
2.3.3 繪制
從demo演示中可以看出,該控件并沒有什么復(fù)雜的場景,只是計算矩形的寬度并繪制矩形、文字、下劃線而已,繪制View需要重寫onDraw(Canvas)方法。
在重寫該方法之前,我們需要定義繪制時候的畫筆并進行初始化,一共要繪制的內(nèi)容有三個,矩形、文字、下劃線,而矩形和下劃線各自都有兩種狀態(tài),而文字直接可以用Canvas來繪制,則需要的畫筆一共有四個~
private Paint mSelectedBackgroundPaint;
private Paint mNormalBackgroundPaint;
private Paint mBottomSelectedPaint;
private Paint mBottomNormalPaint;
然后進行畫筆的初始化操作,畫筆的初始化可以在初始化屬性之后調(diào)用。
private void initPaint() {
mSelectedBackgroundPaint = new Paint();
mSelectedBackgroundPaint.setColor(mSelectedBackgroundColor);
mNormalBackgroundPaint = new Paint();
mNormalBackgroundPaint.setColor(getColor(android.R.color.transparent));
mBottomSelectedPaint = new Paint();
mBottomNormalPaint = new Paint();
mBottomSelectedPaint.setColor(mBottomSelectedColor);
mBottomNormalPaint.setColor(mBottomNormalColor);
mBottomSelectedPaint.setStrokeWidth(mBottomLineHeight);
mBottomNormalPaint.setStrokeWidth(mBottomLineHeight);
}
接下來,我們需要根據(jù)驗證碼的位數(shù)繪制不同的矩形顏色和下劃線顏色,定義一個全局變量mCurrentPosition 在繪制的時候使用,然后重寫onDraw(Canvas)方法。
@Override
protected void onDraw(Canvas canvas) {
mCurrentPosition = getText().length(); //獲取驗證碼位數(shù)
//支持padding屬性
int width = mEachRectLength - getPaddingLeft() - getPaddingRight();
int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
//循環(huán)繪制
for (int i = 0; i < mFigures; i++) {
canvas.save();
int start = width * i + i * mVerCodeMargin;
int end = width + start;
//畫一個矩形
if (i == mCurrentPosition) {//選中的下一個狀態(tài)
canvas.drawRect(start, 0, end, height, mSelectedBackgroundPaint);
} else {
canvas.drawRect(start, 0, end, height, mNormalBackgroundPaint);
}
canvas.restore();
}
//繪制文字
String value = getText().toString();
for (int i = 0; i < value.length(); i++) {
canvas.save();
int start = width * i + i * mVerCodeMargin;
float x = start + width / 2;
TextPaint paint = getPaint();
paint.setTextAlign(Paint.Align.CENTER);
paint.setColor(getCurrentTextColor());
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float baseline = (height - fontMetrics.bottom + fontMetrics.top) / 2
- fontMetrics.top;
canvas.drawText(String.valueOf(value.charAt(i)), x, baseline, paint);
canvas.restore();
}
//繪制底線
for (int i = 0; i < mFigures; i++) {
canvas.save();
float lineY = height - mBottomLineHeight / 2;
int start = width * i + i * mVerCodeMargin;
int end = width + start;
if (i < mCurrentPosition) {
canvas.drawLine(start, lineY, end, lineY, mBottomSelectedPaint);
} else {
canvas.drawLine(start, lineY, end, lineY, mBottomNormalPaint);
}
canvas.restore();
}
}
以上代碼中繪制矩形和下劃線并沒有什么復(fù)雜的,主要是在繪制文字的時候需要注意文字的居中問題,關(guān)于該問題可以參考文章Android Canvas drawText實現(xiàn)中文垂直居中
2.3.4 防止輸入溢出
在輸入驗證碼之后,我們需要對其進行邊界控制,默認(rèn)會一直向后繪制,這樣在超出驗證碼位數(shù)后點擊刪除則不會第一時間刪除最后一位驗證碼,這也是為了邏輯的完整性,我們需要對輸入內(nèi)容進行監(jiān)聽,則需要調(diào)用方法addTextChangedListener(),該方法可以在構(gòu)造方法中進行調(diào)用。
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
mCurrentPosition = getText().length();
postInvalidate();
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mCurrentPosition = getText().length();
postInvalidate();
if (onCodeChangedListener != null) {
onCodeChangedListener.onVerCodeChanged(getText(), start, before, count);
}
}
@Override
public void afterTextChanged(Editable s) {
mCurrentPosition = getText().length();
postInvalidate();
if (getText().length() == mFigures) {
if (onCodeChangedListener != null) {
onCodeChangedListener.onInputCompleted(getText());
}
} else if (getText().length() > mFigures) {
getText().delete(mFigures, getText().length());//防止輸入溢出
}
}
以上代碼在防止輸入溢出的同時監(jiān)聽了驗證碼輸入內(nèi)容變化時候的監(jiān)聽。
以上是對該控件的代碼分析過程,因為控件比較簡單,沒有進行詳細(xì)的說明,要是有問題請留言,同時該項目已經(jīng)上傳至github中,項目地址VercideEditText