1. 程式人生 > >VerificationCodeView 驗證碼自定義控制元件原始碼解析

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