Android 短信验证码输入框

日常开发中,我们会遇到一些很炫酷的手机短信验证码输入,但系统自带的EditText显然并不能满足所需的短信验证码输入框,因此,在系统自带的EditText基础上自定义短信验证码输入框。

实现思路是:定义类继承EditText,重写onMeasure、onDraw方法,添加onTextChanged监听文本输入,利用Canvas绘制输入文本、输入框及光标图形等。

【输入框背景edit_code_bg.xml(注:具体样式可自主定义)】

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/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="http://schemas.android.com/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);
    }

    @Override
    public boolean onTextContextMenuItem(int id) {
        return false;
    }

    @Override
    protected 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);

    }

    @Override
    protected 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);
    }

    @Override
    protected 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(TypedValue.COMPLEX_UNIT_DIP, paramFloat, context.getResources().getDisplayMetrics());
    }
}

最终的实现效果图

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值