验证码输入框

整体思路:底部一个透明的EditText,上面放置多个框框,通过组合布局继承RelativeLayout,在输入的过程中不断控制EditText的paddingLeft

Java代码

public class VerificationCodeView extends RelativeLayout implements TextWatcher, View.OnKeyListener {
    private Context mContext;
    private static final int INIT_POS = 0;

    private int mVerifyNum;
    private float mTextSize;
    private int mVerifyNormalBg;
    private int mVerifyHighlightBg;
    private int mVerifyNormalColor;
    private int mVerifyErrorBg;
    private int mVerifyErrorColor;
    private float mVerifyWidth;
    private float mVerifyHeight;
    private float mVerifyMargin;

    private LinearLayout mTvContainer;
    private EditText mEditText;
    private TextView mTvErrorInfo;
    private int mCurPos = INIT_POS;
    private VerificationCodeCallBack mVerificationCodeCallBack;
    private ObjectAnimator shakeAnimator;

    public VerificationCodeView(Context context) {
        this(context, null);
    }

    public VerificationCodeView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VerificationCodeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        initAttrs(attrs);
        initViews();
    }

    public void setVerificationCodeCallBack(VerificationCodeCallBack verificationCodeCallBack) {
        mVerificationCodeCallBack = verificationCodeCallBack;
    }

    private void initAttrs(AttributeSet attrs) {
        TypedArray array = mContext.obtainStyledAttributes(attrs, R.styleable.VerificationCodeView);
        mVerifyNum = array.getInt(R.styleable.VerificationCodeView_verify_num, 4);
        mTextSize = array.getDimension(R.styleable.VerificationCodeView_android_textSize, 27);
        mVerifyNormalBg = array.getResourceId(R.styleable.VerificationCodeView_verify_normal_bg, R.drawable.verify_normal_bg);
        mVerifyHighlightBg = array.getResourceId(R.styleable.VerificationCodeView_verify_highlight_bg, R.drawable.verify_highlight_bg);
        mVerifyNormalColor = array.getColor(R.styleable.VerificationCodeView_verify_normal_color, Color.BLACK);
        mVerifyErrorBg = array.getResourceId(R.styleable.VerificationCodeView_verify_error_bg, R.drawable.verify_error_bg);
        mVerifyErrorColor = array.getColor(R.styleable.VerificationCodeView_verify_error_color, Color.parseColor("#FF7B1A"));
        mVerifyWidth = array.getDimension(R.styleable.VerificationCodeView_verify_width, AppUtils.dp2px(40, getResources().getDisplayMetrics()));
        mVerifyHeight = array.getDimension(R.styleable.VerificationCodeView_verify_height, AppUtils.dp2px(55, getResources().getDisplayMetrics()));
        mVerifyMargin = array.getDimension(R.styleable.VerificationCodeView_verify_margin, AppUtils.dp2px(15, getResources().getDisplayMetrics()));
        array.recycle();
    }

    private void initViews() {
        LayoutInflater.from(mContext).inflate(R.layout.view_verification, this);
        mTvContainer = findViewById(R.id.tv_container);
        for (int i = 0; i < mVerifyNum; i++) {
            TextView textView = new TextView(mContext);
            textView.setTextColor(mVerifyNormalColor);
            textView.setTextSize(mTextSize);
            textView.setBackgroundResource(mVerifyNormalBg);
            textView.setGravity(Gravity.CENTER);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams((int) mVerifyWidth, (int) mVerifyHeight);
            if (i != 0) {
                params.leftMargin = (int) mVerifyMargin;
            }
            mTvContainer.addView(textView, params);
        }
        mEditText = findViewById(R.id.verify_edit_text);
        mEditText.addTextChangedListener(this);
        mEditText.setOnKeyListener(this);
        mTvErrorInfo = findViewById(R.id.tv_error_info);
        mTvErrorInfo.setTextColor(mVerifyErrorColor);
        nextPos("");
    }

    private void nextPos(String text) {
        //显示刚刚输入的文字
        if (mCurPos >= 0 && mCurPos < mVerifyNum) {
            TextView textView = (TextView) mTvContainer.getChildAt(mCurPos);
            textView.setText(text);

            //光标移向下个位置
            if (!TextUtils.isEmpty(text)) {
                mCurPos++;
            }
            if (mCurPos == 1 && mTvErrorInfo.getVisibility() == View.VISIBLE) {
                //之前输入错误,等到输入一位再隐藏输入信息
                mTvErrorInfo.setVisibility(View.INVISIBLE);
            }
            setEditTextPadding();
            setSelectHighLight();

            if (mCurPos == mVerifyNum) {
                //表示输入完成
                mEditText.setCursorVisible(false);
                if (mVerificationCodeCallBack != null) {
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < mTvContainer.getChildCount(); i++) {
                        TextView tv = (TextView) mTvContainer.getChildAt(i);
                        sb.append(tv.getText());
                    }
                    mVerificationCodeCallBack.onInputEnd(sb.toString());
                }
            }
        }
    }

    private void prePos() {
        //光标需要往前移动一个位置
        if (mCurPos > 0 && mCurPos <= mVerifyNum) {
            if (mCurPos == mVerifyNum) {
                mEditText.setCursorVisible(true);
            }
            mCurPos--;
            if (mCurPos >= 0 && mCurPos < mVerifyNum) {
                TextView textView1 = (TextView) mTvContainer.getChildAt(mCurPos);
                textView1.setText("");
                setEditTextPadding();
                setSelectHighLight();
            }
        }
    }

    /**
     * 控制光标位置
     */
    private void setEditTextPadding() {
        int paddingLeft = (int) (mCurPos * mVerifyWidth + mCurPos * mVerifyMargin + mVerifyWidth / 2);
        mEditText.setPadding(paddingLeft, 0, 0, 0);
    }

    /**
     * 控制高亮显示
     */
    private void setSelectHighLight() {
        for (int i = 0; i < mTvContainer.getChildCount(); i++) {
            TextView textView = (TextView) mTvContainer.getChildAt(i);
            if (mCurPos == i) {
                textView.setBackgroundResource(mVerifyHighlightBg);
            } else {
                textView.setBackgroundResource(mVerifyNormalBg);
            }
        }
    }

    public void inputError() {
        inputError(null);
    }

    public void inputError(String errorMsg) {
        for (int i = 0; i < mTvContainer.getChildCount(); i++) {
            TextView textView = (TextView) mTvContainer.getChildAt(i);
            textView.setTextColor(mVerifyErrorColor);
            textView.setBackgroundResource(mVerifyErrorBg);
        }
        mTvErrorInfo.setVisibility(View.VISIBLE);
        if (!TextUtils.isEmpty(errorMsg)) {
            mTvErrorInfo.setText(errorMsg);
        }
        if (shakeAnimator == null) {
            shakeAnimator = ObjectAnimator.ofFloat(mTvErrorInfo, "translationX", 0, 10);
            shakeAnimator.setDuration(60);
            shakeAnimator.setRepeatCount(5);
            shakeAnimator.setRepeatMode(ValueAnimator.REVERSE);
            shakeAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    clearInput();
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
        }
        shakeAnimator.start();
    }

    /**
     * 清除所有输入
     */
    public void clearInput() {
        for (int i = 0; i < mTvContainer.getChildCount(); i++) {
            TextView textView = (TextView) mTvContainer.getChildAt(i);
            textView.setTextColor(mVerifyNormalColor);
            textView.setText("");
        }
        mCurPos = INIT_POS;
        mEditText.setCursorVisible(true);
        nextPos("");
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        String s1 = s.toString();
        if (!TextUtils.isEmpty(s1)) {
            if (s1.length() == 1) {
                nextPos(s1);
            } else {
                //为了复制粘贴
                int min = Math.min(mVerifyNum, s1.length());
                for (int i = 0; i < min; i++) {
                    char c = s1.charAt(i);
                    nextPos(String.valueOf(c));
                }
            }
            mEditText.setText("");
        }
    }

    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_UP) {
            prePos();
            return true;
        }
        return false;
    }

    public interface VerificationCodeCallBack {
        void onInputEnd(String inputCode);
    }

    public EditText getEditText() {
        return mEditText;
    }
}

布局

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <LinearLayout
        android:id="@+id/tv_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:orientation="horizontal" />

    <EditText
        android:id="@+id/verify_edit_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/tv_container"
        android:layout_alignLeft="@+id/tv_container"
        android:layout_alignRight="@+id/tv_container"
        android:layout_alignTop="@+id/tv_container"
        android:layout_centerHorizontal="true"
        android:background="@color/transparent"
        android:gravity="center_vertical"
        android:inputType="number"
        android:textCursorDrawable="@drawable/verify_edit_cursor" />

    <TextView
        android:id="@+id/tv_error_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/verify_edit_text"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:text="验证码错误,请重新输入"
        android:textSize="12sp"
        android:visibility="invisible" />

</merge>

style属性

<declare-styleable name="VerificationCodeView">
        <attr name="verify_num" format="integer" />
        <attr name="android:textSize" />
        <attr name="verify_normal_bg" format="reference" />
        <attr name="verify_highlight_bg" format="reference" />
        <attr name="verify_normal_color" format="color" />
        <attr name="verify_error_bg" format="reference" />
        <attr name="verify_error_color" format="color" />
        <attr name="verify_width" format="dimension" />
        <attr name="verify_height" format="dimension" />
        <attr name="verify_margin" format="dimension" />
    </declare-styleable>

效果

输入中

输入错误

输入错误重新输入

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值