Android View自定义验证码输入框

完整代码Demo下载地址

首先,我们来看看实现的是怎么样的效果:

 

思路

完全重画一个EditText,就包含了测量布局重新绘制这两个关键步骤。好了,到这里理一下整体的思路:

  • 根据验证码个数以及边框大小来计算输入框显示的宽度
  • 覆盖原来的EditText画布,重新绘制方框
  • 根据输入的索引来确定高亮的方框
  • 重写onTextChanged 但满足验证码个数的时候调用自动完成方法

开始

准备开始了,果断继承一个AppCompatEditText 来初始化基本参数先:

  • 验证码个数
  • 输入方框的大小
  • 边框的大小及间距
  • 输入框背景色和光标颜色
/**
 * 验证码输入框,重写EditText的绘制方法实现。
 */
public class InputEditText extends AppCompatEditText {


    private int mTextColor;

    public interface OnTextFinishListener {

        void onTextFinish(CharSequence text, int length);
    }

    // 输入的最大长度
    private int mMaxLength = 4;
    // 边框宽度
    private int mStrokeWidth;
    // 边框高度
    private int mStrokeHeight;
    // 边框之间的距离
    private int mStrokePadding = 20;

    private final Rect mRect = new Rect();


    /**
     * 输入结束监听
     */
    private OnTextFinishListener mOnInputFinishListener;

    // 方框的背景
    private Drawable mStrokeDrawable;

    //光标的背景
    private Drawable mCursorDrawable;

    //光标的矩形
    private final Rect mIndexRect = new Rect();

    /**
     * 构造方法
     */
    public InputEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.InputEditText);
        this.mStrokeHeight = (int) ta.getDimension(R.styleable.InputEditText_strokeHeight, 60);
        this.mStrokeWidth = (int) ta.getDimension(R.styleable.InputEditText_strokeWidth, 60);
        this.mStrokePadding = (int) ta.getDimension(R.styleable.InputEditText_strokePadding, 20);
        this.mStrokeDrawable = ta.getDrawable(R.styleable.InputEditText_strokeBackground);
        this.mMaxLength = ta.getInteger(R.styleable.InputEditText_strokeLength, 4);
        this.mCursorDrawable = ta.getDrawable(R.styleable.InputEditText_textCursorDrawable);

        ;//获取系统中的Drawable
        ta.recycle();

        if (mStrokeDrawable == null) {
            throw new NullPointerException("stroke drawable not allowed to be null!");
        }
        if (mStrokeDrawable == null) {
            throw new NullPointerException("stroke drawable not allowed to be null!");
        }
        setMaxLength(mMaxLength);
        setLongClickable(false);
        // 去掉背景颜色
        setBackgroundColor(Color.TRANSPARENT);
        // 不显示光标
        setCursorVisible(false);
    }

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

//    private int px(int size) {
//        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, getResources().getDisplayMetrics());
//    }

    /**
     * 设置最大长度
     */
    private void setMaxLength(int maxLength) {
        if (maxLength >= 0) {
            setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
        } else {
            setFilters(new InputFilter[0]);
        }
    }

 测量布局

初始化完了就要开始测量布局了,计算公式为:

输入框宽度 = 边框宽度 * 数量 + 边框间距 *(数量-1)

    @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);
        // 重绘文本
        drawText(canvas);
    }

绘制背景方框

    /**
     * 重绘背景
     */
    private void drawStrokeBackground(Canvas canvas) {
        // 绘制方框背景颜色
        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);

            //这里再次绘制光标
            drawCursor(canvas, mRect);

        }
    }

绘制光标

    /*
     * 绘制光标
     */

    public void drawCursor(Canvas canvas, Rect rect) {
        //这里默认光标的宽度是4px,后续可以在attr中加入自定义设置
        mIndexRect.left = rect.left + rect.width() / 2 - 2;
        mIndexRect.top = rect.top + rect.height() / 4;
        mIndexRect.right = rect.left + rect.width() / 2 + 2;
        mIndexRect.bottom = rect.bottom - rect.height() / 4;
        mCursorDrawable.setBounds(mIndexRect);
        mCursorDrawable.draw(canvas);
    }

画验证码文字


    /**
     * 重绘文本
     */
    private void drawText(Canvas canvas) {
        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() / 2 + 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) {
            hideSoftInput();
            if (mOnInputFinishListener != null) {
                mOnInputFinishListener.onTextFinish(getEditableText().toString(), mMaxLength);
            }
        }

    }

以上就是自定义验证码输入框全部内容了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值