VerificationCodeView 验证码自定义控件源码解析

https://github.com/JackTuoTuo/VerificationCodeView

喜欢该控件请给作者start.

演示

演示

原理

在布局中文件中使用了一个透明的EditText来接受用户的输入事件, 在布局文件的LinearLayout中动态添加正方形输入框,正方形输入框其实是一个个的TextView。

初始化逻辑

  • 初始化UI
// 初始UI
private void initUI() {
    //创建TextView数组
    initTextViews(getContext(), mEtNumber, mEtWidth, mEtDividerDrawable, mEtTextSize, mEtTextColor);
    //将TextView控件加入进容器中
    initEtContainer(mTextViews);
    //设置监听
    setListener();
}
  • initTextViews()方法,该方法主要是创建一个TextView数组,存储需要得TextView
//初始化TextView
private void initTextViews(Context context, int etNumber, int etWidth, Drawable etDividerDrawable, float etTextSize, int etTextColor) {
    et.setCursorVisible(false);//将光标隐藏
    et.setFilters(new InputFilter[]{new InputFilter.LengthFilter(etNumber)}); //最大输入长度
    // 设置分割线的宽度
    if (etDividerDrawable != null) {
        etDividerDrawable.setBounds(0, 0, etDividerDrawable.getMinimumWidth(), etDividerDrawable.getMinimumHeight());
        containerEt.setDividerDrawable(etDividerDrawable);
    }
    mTextViews = new TextView[etNumber]; //创建一个储存输入的数据 TextView 控件数组
    for (int i = 0; i < mTextViews.length; i++) {
        TextView textView = new TextView(context);
        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, etTextSize);
        textView.setTextColor(etTextColor);
        textView.setWidth(etWidth);
        textView.setHeight(etWidth);
        if (i == 0) {
            textView.setBackgroundDrawable(mEtBackgroundDrawableFocus);
        } else {
            textView.setBackgroundDrawable(mEtBackgroundDrawableNormal);
        }
        textView.setGravity(Gravity.CENTER);
        textView.setFocusable(false);
        mTextViews[i] = textView;
    }
    //若没有设置明文密文按钮背景图则明文密文按钮隐藏
    if (mEtToggleIconOpen != null && mEtToggleIconClose != null) {
       this.findViewById(R.id.btn_watch_paw).setVisibility(VISIBLE);
       this.findViewById(R.id.btn_watch_paw).setBackgroundDrawable(mEtToggleIconClose);
    }
}
  • initEtContainer()方法,该方法是将创建好的TextView加入进LinerLayout容器中
private void initEtContainer(TextView[] mTextViews) {
    for (TextView mTextView : mTextViews) {
        //LinearLayout容器,存储TextView对象
        containerEt.addView(mTextView);
    }
}
  • setListener()为控件设置监听事件
private void setListener() {
    // 监听输入内容
    et.addTextChangedListener(myTextWatcher);
    // 监听删除按键
    et.setOnKeyListener(new OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
                onKeyDelete();
                return true;
            }
            return false;
        }
    });
    //监听明文密文切换展示按钮
    this.findViewById(R.id.btn_watch_paw).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mEtToggleIconOpen != null && mEtToggleIconClose != null) {
                if (!mEtPwd) {
                    //当VerificationCodeView 中最后一个TextView也写入了数据,明文和密码切换按钮才生效
                    if (!TextUtils.isEmpty(mTextViews[mEtNumber - 1].getText().toString().trim())) {
                        showCiphertext();
                        mEtPwd = true;
                    }
                } else {
                    if (!TextUtils.isEmpty(mTextViews[mEtNumber - 1].getText().toString().trim())) {
                        showPlaintext();
                        mEtPwd = false;
                    }
                }
            }
        }
    });
}

控件监听逻辑

  • EditText设置输入监听,接受用户输入的内容,et.addTextChangedListener(myTextWatcher);
private class MyTextWatcher implements TextWatcher {
    @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 editable) {
        String inputStr = editable.toString();
        if (!TextUtils.isEmpty(inputStr)) {
            //这里考虑到输入多个内容的情况,切割之后遍历放入TextView中
            String[] strArray = inputStr.split("");
            for (int i = 0; i < strArray.length; i++) {
                // 不能大于输入框个数
                if (i > mEtNumber) {
                    break;
                }
                //将输入的内容放入TextView中
                setText(strArray[i]);
                et.setText("");
            }
        }
    }
}

// 给TextView 设置文字
private void setText(String inputContent) {
    for (int i = 0; i < mTextViews.length; i++) {
        final TextView tv = mTextViews[i];
        if (tv.getText().toString().trim().equals("")) {
            tv.setText(inputContent);
            if (mEtPwd) {
                mRefreshHandler.postDelayed(new Runnable() {// 将改变TextView属性为展示密文的属性,我改动的
                    @Override
                    public void run() {
                        //等待200毫秒,将TextView内容显示模式设置为密文
                        tv.setTransformationMethod(new CustomPasswordTransformationMethod(mEtPwdMode));
                    }
                }, 200);
            } else {
                mRefreshHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //马上将TextView内容显示模式设置为明文
                        tv.setTransformationMethod(SingleLineTransformationMethod.getInstance());
                    }
                }, 0);
            }
            // 添加输入完成的监听
            if (inputCompleteListener != null) {
                //对外暴露输入监听事件
                inputCompleteListener.inputComplete();
            }
            tv.setBackgroundDrawable(mEtBackgroundDrawableNormal);
            if (i < mEtNumber - 1) {
                mTextViews[i + 1].setBackgroundDrawable(mEtBackgroundDrawableFocus);
            }
            break;
        }
    }
    //当输入内容显示到最后一个TextView时候,若用户设置了明文密文切换按钮的背景,则将按钮显示并且设置相应的背景。
    if (!TextUtils.isEmpty(mTextViews[mEtNumber - 1].getText().toString())) {
        if (mEtToggleIconOpen != null && mEtToggleIconClose != null) {
            this.findViewById(R.id.btn_watch_paw).setVisibility(VISIBLE);
            this.findViewById(R.id.btn_watch_paw).setBackgroundDrawable(mEtToggleIconOpen);
        }
    }
}

  • EditText设置删除监听
// 监听删除
private void onKeyDelete() {
    for (int i = mTextViews.length - 1; i >= 0; i--) {
        TextView tv = mTextViews[i];
        if (!tv.getText().toString().trim().equals("")) {
            //如果该TextView内容不为空,就将TextView设置为空
            tv.setText("");
            // 添加删除完成监听
            if (inputCompleteListener != null) {
                //对外暴露删除监听事件
                inputCompleteListener.deleteContent();
            }
            tv.setBackgroundDrawable(mEtBackgroundDrawableFocus);
            if (i < mEtNumber - 1) {
                mTextViews[i + 1].setBackgroundDrawable(mEtBackgroundDrawableNormal);
            }
            break;
        }
    }
    //当最后一个TextView内容为空,若用户设置了明文密文切换按钮的背景,则将按钮显示并且设置相应的背景。
    if (TextUtils.isEmpty(mTextViews[mEtNumber - 1].getText().toString())) {
        if (mEtToggleIconOpen != null && mEtToggleIconClose != null) {
            this.findViewById(R.id.btn_watch_paw).setVisibility(VISIBLE);
            this.findViewById(R.id.btn_watch_paw).setBackgroundDrawable(mEtToggleIconClose);
        }
    }
}
  • 为明文密文切换按钮设置监听事件
//监听明文密文切换展示按钮
this.findViewById(R.id.btn_watch_paw).setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        if (mEtToggleIconOpen != null && mEtToggleIconClose != null) {
            if (!mEtPwd) {
                //当VerificationCodeView 中最后一个TextView也写入了数据,明文和密码切换按钮才生效
                if (!TextUtils.isEmpty(mTextViews[mEtNumber - 1].getText().toString().trim())) {
                    showCiphertext();
                    mEtPwd = true;
                }
            } else {
                if (!TextUtils.isEmpty(mTextViews[mEtNumber - 1].getText().toString().trim())) {
                    showPlaintext();
                    mEtPwd = false;
                }
            }
        }
    }
});

地址

  • 该源码解读是基于1.0.6版本。
  • GitHub

猜你喜欢

转载自blog.csdn.net/MoLiao2046/article/details/85616840