日常开发中,我们会遇到一些很炫酷的手机短信验证码输入,但系统自带的EditText显然并不能满足所需的短信验证码输入框,因此,在系统自带的EditText基础上自定义短信验证码输入框。
实现思路是:定义类继承EditText,重写onMeasure、onDraw方法,添加onTextChanged监听文本输入,利用Canvas绘制输入文本、输入框及光标图形等。
【输入框背景edit_code_bg.xml(注:具体样式可自主定义)】
<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="/apk/res/android"><item android:state_focused="false"><shape><solid android:color="@android:color/transparent" /><corners android:radius="4dp" /><stroke android:width="1dp" android:color="#F1F1F1" /></shape></item><item android:state_focused="true"><shape><solid android:color="@android:color/transparent" /><corners android:radius="4dp" /><stroke android:width="1dp" android:color="#FFA500" /></shape></item></selector>
【光标背景edit_cursor_style.xml(注:具体样式可自主定义)】
<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="/apk/res/android"><item android:state_focused="false"><shape android:shape="rectangle"><solid android:color="@android:color/transparent" /><size android:width="1dp" /></shape></item><item android:state_focused="true"><shape android:shape="rectangle"><solid android:color="#FFA500" /><size android:width="1dp" /></shape></item></selector>
【自定义EditText】
import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Rect;import android.graphics.drawable.Drawable;import android.text.InputFilter;import android.text.TextPaint;import android.util.AttributeSet;import android.view.inputmethod.InputMethodManager;import android.widget.EditText;public class CodeEditText extends EditText {public interface OnTextFinishListener {void onTextFinish(CharSequence text, int length);}private int mTextColor;// 输入的最大长度private int mMaxLength;// 边框宽度private int mStrokeWidth;// 边框高度private int mStrokeHeight;// 边框之间的距离private int mStrokePadding;// 光标宽度private int mCursorWidth;// 光标高度private int mCursorHeight;// 方框的背景private Drawable mStrokeDrawable;// 光标的背景private Drawable mCursorDrawable;// 输入结束监听private OnTextFinishListener mOnInputFinishListener;// 是否光标获取焦点private boolean mCursorStateFocused = true;// 记录上次光标获取焦点时间private long mLastCursorFocusedTimeMillis = System.currentTimeMillis();public void setStrokeWidth(int strokeWidth) {mStrokeWidth = strokeWidth;}public void setStrokeHeight(int strokeHeight) {mStrokeHeight = strokeHeight;}public void setStrokePadding(int strokePadding) {mStrokePadding = strokePadding;}public void setCursorWidth(int cursorWidth) {mCursorWidth = cursorWidth;}public void setCursorHeight(int cursorHeight) {mCursorHeight = cursorHeight;}public void setStrokeDrawable(Drawable strokeDrawable) {mStrokeDrawable = strokeDrawable;}public void setCursorDrawable(Drawable cursorDrawable) {mCursorDrawable = cursorDrawable;}/*** 设置最大长度*/public void setMaxLength(int maxLength) {mMaxLength = maxLength;if (maxLength >= 0) {setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});} else {setFilters(new InputFilter[0]);}}/*** 构造方法**/public CodeEditText(Context context) {this(context, null);}public CodeEditText(Context context, AttributeSet attrs) {super(context, attrs);mStrokeWidth = toDip(context, 30);mStrokeHeight = toDip(context, 30);mStrokePadding = toDip(context, 10);mCursorWidth = toDip(context, 1);mCursorHeight = toDip(context, 20);mStrokeDrawable = context.getResources().getDrawable(R.drawable.edit_code_bg);mCursorDrawable = context.getResources().getDrawable(R.drawable.edit_cursor_style);mMaxLength = 6;setMaxLength(mMaxLength);setLongClickable(false);// 去掉背景颜色setBackgroundColor(Color.TRANSPARENT);// 不显示光标setCursorVisible(false);setFocusable(true);setFocusableInTouchMode(true);}@Overridepublic boolean onTextContextMenuItem(int id) {return false;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = getMeasuredWidth();int height = getMeasuredHeight();int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);// 判断高度是否小于推荐高度if (height < mStrokeHeight) {height = mStrokeHeight;}// 判断高度是否小于推荐宽度int recommendWidth = mStrokeWidth * mMaxLength + mStrokePadding * (mMaxLength - 1);if (width < recommendWidth) {width = recommendWidth;}widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, widthMode);heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, heightMode);setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onDraw(Canvas canvas) {mTextColor = getCurrentTextColor();setTextColor(Color.TRANSPARENT);super.onDraw(canvas);setTextColor(mTextColor);// 重绘背景颜色drawStrokeBackground(canvas);drawCursorBackground(canvas);// 重绘文本drawText(canvas);}/*** 重绘背景*/private void drawStrokeBackground(Canvas canvas) {Rect mRect = new Rect();if (mStrokeDrawable != null) {// 绘制方框背景mRect.left = 0;mRect.top = 0;mRect.right = mStrokeWidth;mRect.bottom = mStrokeHeight;int count = canvas.getSaveCount();canvas.save();for (int i = 0; i < mMaxLength; i++) {mStrokeDrawable.setBounds(mRect);mStrokeDrawable.setState(new int[]{android.R.attr.state_enabled});mStrokeDrawable.draw(canvas);float dx = mRect.right + mStrokePadding;// 移动画布canvas.save();canvas.translate(dx, 0);}canvas.restoreToCount(count);canvas.translate(0, 0);// 绘制激活状态// 当前激活的索引int activatedIndex = Math.max(0, getEditableText().length());if (activatedIndex < mMaxLength) {mRect.left = mStrokeWidth * activatedIndex + mStrokePadding * activatedIndex;mRect.right = mRect.left + mStrokeWidth;mStrokeDrawable.setState(new int[]{android.R.attr.state_focused});mStrokeDrawable.setBounds(mRect);mStrokeDrawable.draw(canvas);}}}/*** 重绘光标*/private void drawCursorBackground(Canvas canvas) {Rect mRect = new Rect();if (mCursorDrawable != null) {// 绘制光标mRect.left = (mStrokeWidth - mCursorWidth) / 2;mRect.top = (mStrokeHeight - mCursorHeight) / 2;mRect.right = mRect.left + mCursorWidth;mRect.bottom = mRect.top + mCursorHeight;int count = canvas.getSaveCount();canvas.save();for (int i = 0; i < mMaxLength; i++) {mCursorDrawable.setBounds(mRect);mCursorDrawable.setState(new int[]{android.R.attr.state_enabled});mCursorDrawable.draw(canvas);float dx = mRect.right + mStrokePadding;// 移动画布canvas.save();canvas.translate(dx, 0);}canvas.restoreToCount(count);canvas.translate(0, 0);// 绘制激活状态// 当前激活的索引int activatedIndex = Math.max(0, getEditableText().length());if (activatedIndex < mMaxLength) {mRect.left = mStrokeWidth * activatedIndex + mStrokePadding * activatedIndex + (mStrokeWidth - mCursorWidth) / 2;mRect.right = mRect.left + mCursorWidth;int[] state = new int[]{isFocusable() && isFocusableInTouchMode() && mCursorStateFocused ? android.R.attr.state_focused : android.R.attr.state_enabled};mCursorDrawable.setState(state);mCursorDrawable.setBounds(mRect);mCursorDrawable.draw(canvas);if ((System.currentTimeMillis() - mLastCursorFocusedTimeMillis) >= 800) {mCursorStateFocused = !mCursorStateFocused;mLastCursorFocusedTimeMillis = System.currentTimeMillis();}}}}/*** 重绘文本*/private void drawText(Canvas canvas) {Rect mRect = new Rect();int count = canvas.getSaveCount();canvas.translate(0, 0);int length = getEditableText().length();for (int i = 0; i < length; i++) {String text = String.valueOf(getEditableText().charAt(i));TextPaint textPaint = getPaint();textPaint.setColor(mTextColor);// 获取文本大小textPaint.getTextBounds(text, 0, 1, mRect);// 计算(x,y) 坐标int x = mStrokeWidth / 2 + (mStrokeWidth + mStrokePadding) * i - (mRect.centerX());int y = (canvas.getHeight() + mRect.height()) / 2;canvas.drawText(text, x, y, textPaint);}canvas.restoreToCount(count);}@Overrideprotected void onTextChanged(CharSequence text, int start,int lengthBefore, int lengthAfter) {super.onTextChanged(text, start, lengthBefore, lengthAfter);// 当前文本长度int textLength = getEditableText().length();if (textLength == mMaxLength) {clearFocus();hideSoftInput();if (mOnInputFinishListener != null) {mOnInputFinishListener.onTextFinish(getEditableText().toString(), mMaxLength);}}}public void hideSoftInput() {InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);if (imm != null)imm.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);}/*** 设置输入完成监听*/public void setOnTextFinishListener(OnTextFinishListener onInputFinishListener) {this.mOnInputFinishListener = onInputFinishListener;}/*** 转 dp 值* @param context* @param paramFloat* @return*/public static int toDip(Context context, float paramFloat) {return (int) TypedValue.applyDimension(PLEX_UNIT_DIP, paramFloat, context.getResources().getDisplayMetrics());}}
最终的实现效果图