Android 自定义 View-->验证码输入框
Posted Kevin-Dev
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 自定义 View-->验证码输入框相关的知识,希望对你有一定的参考价值。
概述
先描述一下具体需求吧,我们在项目中可能会遇到修改用户名及密码的需求,为保证一定的完全性,服务端一般会接入短信验证码的功能。我们需要将接受到的验证码返回给服务端进行验证。可能会有以下的界面让用户输入验证码:
实现
1. 特性
- 支持设置框数量
- 支持设置框的风格样式
- 支持根据状态区分框颜色
- 基于EditText实现,更优雅
2. 效果图
3. 属性
4. 代码
- attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SplitEditText">
<attr name="setStrokeWidth" format="dimension"/>
<attr name="setBorderColor" format="color"/>
<attr name="setInputBorderColor" format="color"/>
<attr name="setFocusBorderColor" format="color"/>
<attr name="setBoxBackgroundColor" format="color"/>
<attr name="setBorderCornerRadius" format="dimension"/>
<attr name="setBorderSpacing" format="dimension"/>
<attr name="setMaxLength" format="integer"/>
<attr name="setBorderStyle" format="enum">
<enum name="box" value="0"/>
<enum name="line" value="1"/>
</attr>
<attr name="setTextStyle" format="enum">
<enum name="plain_text" value="0"/>
<enum name="cipher_text" value="1"/>
</attr>
<attr name="setCipherMask" format="string"/>
<attr name="setFakeBoldText" format="boolean"/>
</declare-styleable>
</resources>
- SplitEditText.java
public class SplitEditText extends AppCompatEditText
/**
* 画笔
*/
private Paint mPaint;
/**
* 画笔宽度
*/
private float mStrokeWidth;
/**
* 边框颜色
*/
private int mBorderColor = 0xFF666666;
/**
* 输入的边框颜色
*/
private int mInputBorderColor = 0xFF1E90FF;
/**
* 焦点的边框颜色
*/
private int mFocusBorderColor;
/**
* 框的背景颜色
*/
private int mBoxBackgroundColor;
/**
* 框的圆角大小
*/
private float mBorderCornerRadius;
/**
* 框与框之间的间距大小
*/
private float mBorderSpacing;
/**
* 输入框宽度
*/
private float mBoxWidth;
/**
* 输入框高度
*/
private float mBoxHeight;
/**
* 允许输入的最大长度
*/
private int mMaxLength = 6;
/**
* 文本长度
*/
private int mTextLength;
/**
* 路径
*/
private Path mPath;
private RectF mRectF;
private float[] mRadiusFirstArray;
private float[] mRadiusLastArray;
/**
* 边框风格
*/
private @BorderStyle int mBorderStyle = BorderStyle.BOX;
/**
* 边框风格
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(BorderStyle.BOX, BorderStyle.LINE)
public @interface BorderStyle
/**
* 框
*/
int BOX = 0;
/**
* 线
*/
int LINE = 1;
/**
* 文本风格
*/
private @TextStyle int mTextStyle = TextStyle.PLAIN_TEXT;
/**
* 文本风格
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(TextStyle.PLAIN_TEXT, TextStyle.CIPHER_TEXT)
public @interface TextStyle
/**
* 明文
*/
int PLAIN_TEXT = 0;
/**
* 密文
*/
int CIPHER_TEXT = 1;
/**
* 密文掩码
*/
private String mCipherMask;
/**
* 是否是粗体
*/
private boolean isFakeBoldText;
private static final String DEFAULT_CIPHER_MASK = "*";
private boolean isDraw;
private OnTextInputListener mOnTextInputListener;
public SplitEditText(@NonNull Context context)
this(context,null);
public SplitEditText(@NonNull Context context, @Nullable AttributeSet attrs)
this(context, attrs,android.R.attr.editTextStyle);
public SplitEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init(context,attrs);
private void init(@NonNull Context context, @Nullable AttributeSet attrs)
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
mStrokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,1f,displayMetrics);
mBorderSpacing = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,8f,displayMetrics);
setPadding(0,0,0,0);
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.SplitEditText);
final int count = a.getIndexCount();
for (int i = 0; i < count; i++)
int attr = a.getIndex(i);
if(attr == R.styleable.SplitEditText_setStrokeWidth)
mStrokeWidth = a.getDimension(attr,mStrokeWidth);
else if (attr == R.styleable.SplitEditText_setBorderColor)
mBorderColor = a.getColor(attr,mBorderColor);
else if (attr == R.styleable.SplitEditText_setInputBorderColor)
mInputBorderColor = a.getColor(attr,mInputBorderColor);
else if (attr == R.styleable.SplitEditText_setFocusBorderColor)
mFocusBorderColor = a.getColor(attr,mFocusBorderColor);
else if (attr == R.styleable.SplitEditText_setBoxBackgroundColor)
mBoxBackgroundColor = a.getColor(attr,mBoxBackgroundColor);
else if (attr == R.styleable.SplitEditText_setBorderCornerRadius)
mBorderCornerRadius = a.getDimension(attr,mBorderCornerRadius);
else if (attr == R.styleable.SplitEditText_setBorderSpacing)
mBorderSpacing = a.getDimension(attr,mBorderSpacing);
else if (attr == R.styleable.SplitEditText_setMaxLength)
mMaxLength = a.getInt(attr,mMaxLength);
else if (attr == R.styleable.SplitEditText_setBorderStyle)
mBorderStyle = a.getInt(attr,mBorderStyle);
else if (attr == R.styleable.SplitEditText_setTextStyle)
mTextStyle = a.getInt(attr,mTextStyle);
else if (attr == R.styleable.SplitEditText_setCipherMask)
mCipherMask = a.getString(attr);
else if (attr == R.styleable.SplitEditText_setFakeBoldText)
isFakeBoldText = a.getBoolean(attr,false);
a.recycle();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
mPath = new Path();
mRadiusFirstArray = new float[8];
mRadiusLastArray = new float[8];
mRectF = new RectF(0,0,0,0);
if(TextUtils.isEmpty(mCipherMask))
mCipherMask = DEFAULT_CIPHER_MASK;
else if(mCipherMask.length() > 1)
mCipherMask = mCipherMask.substring(0,1);
setBackground(null);
setCursorVisible(false);
setFilters(new InputFilter[]new InputFilter.LengthFilter(mMaxLength));
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w, h, oldw, oldh);
int width = w - getPaddingLeft() - getPaddingRight();
int height = h - getPaddingTop() - getPaddingBottom();
updateSizeChanged(width,height);
private void updateSizeChanged(int width,int height)
//如果框与框之间的间距小于0或者总间距大于控件可用宽度则将间距重置为0
if(mBorderSpacing < 0 || (mMaxLength - 1) * mBorderSpacing > width)
mBorderSpacing = 0;
//计算出每个框的宽度
mBoxWidth = (width - (mMaxLength - 1) * mBorderSpacing) / mMaxLength - mStrokeWidth;
mBoxHeight = height - mStrokeWidth;
@Override
protected void onDraw(Canvas canvas)
//移除super.onDraw(canvas);不绘制EditText相关的
//绘制边框
drawBorders(canvas);
private void drawBorders(Canvas canvas)
isDraw = true;
//遍历绘制未输入文本的框边界
for(int i = mTextLength; i < mMaxLength; i++)
drawBorder(canvas,i,mBorderColor);
int color = mInputBorderColor != 0 ? mInputBorderColor : mBorderColor;
//遍历绘制已输入文本的框边界
for(int i = 0; i < mTextLength; i++)
drawBorder(canvas,i,color);
//绘制焦点框边界
if(mTextLength < mMaxLength && mFocusBorderColor != 0 && isFocused())
drawBorder(canvas,mTextLength,mFocusBorderColor);
private void drawBorder(Canvas canvas,int position,int borderColor)
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setFakeBoldText(false);
mPaint.setColor(borderColor);
//计算出对应的矩形
float left = getPaddingLeft() + mStrokeWidth / 2 + (mBoxWidth + mBorderSpacing) * position;
float top = getPaddingTop() + mStrokeWidth / 2;
mRectF.set(left,top,left + mBoxWidth,top + mBoxHeight);
//边框风格
switch (mBorderStyle)
case BorderStyle.BOX:
drawBorderBox(canvas,position,borderColor);
break;
case BorderStyle.LINE:
drawBorderLine(canvas);
break;
if(mTextLength > position && !TextUtils.isEmpty(getText()))
drawText(canvas,position);
private void drawText(Canvas canvas,int position)
mPaint.setStrokeWidth(0);
mPaint.setColor(getCurrentTextColor());
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setTextSize(getTextSize());
mPaint.setFakeBoldText(isFakeBoldText);
float x = mRectF.centerX();
//y轴坐标 = 中心线 + 文字高度的一半 - 基线到文字底部的距离(也就是bottom)
float y = mRectF.centerY() + (mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top) / 2 - mPaint.getFontMetrics().bottom;
switch (mTextStyle)
case TextStyle.PLAIN_TEXT:
canvas.drawText(String.valueOf(getText().charAt(position)),x,y,mPaint);
break;
case TextStyle.CIPHER_TEXT:
canvas.drawText(mCipherMask,x,y,mPaint);
break;
/**
* 绘制框风格
* @param canvas
* @param position
*/
private void drawBorderBox(Canvas canvas,int position,int borderColor)
if(mBorderCornerRadius > 0)//当边框带有圆角时
if(mBorderSpacing == 0)//当边框之间的间距为0时,只需要开始一个和最后一个框有圆角
if(position == 0 || position == mMaxLength - 1)Android 自定义验证码输入框(支持粘贴连续性)