Android 自定義View之方框驗證碼
Android 自定義View之方框驗證碼
-
最近專案有一個需求,6個方框的驗證碼,要求不能出現游標,然後刪除鍵一個個刪除,輸入完成會回撥。
效果圖.gif
-
剛開始做這個自定義view,我是想著用多個EditText來實現功能,做到後面發現在獲取焦點這個問題上,多個EditText處理不了,於是上網看了別的思路,就是用多個TextView顯示,但是輸入的EditText只有一個,我覺得這個思路可行,自己再次動手改程式碼。
-
具體:這個View就是繼承與RelativeLayout,然後裡面有一個LinearLayout,水平排列放下6個TextView,最後在LinearLayout上蓋上一個字型大小為0的EditText。
1.設想哪些值是可變的:
①驗證碼的字型大小
②驗證碼的字型顏色
③驗證碼框的寬度
④驗證碼框的高度
⑤驗證碼框的預設背景
⑥驗證碼框的焦點背景
⑦驗證碼框之間的間距
⑧驗證碼的個數
- 然後在res/values/下建立一個attr.xml,用於存放這些引數。
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="VerificationCodeView"> <!-- 驗證碼長度 --> <attr name="vCodeDataLength" format="integer" /> <!-- 驗證碼字型大小 --> <attr name="vCodeTextSize" format="dimension" /> <!-- 驗證碼字型顏色 --> <attr name="vCodeTextColor" format="color" /> <!-- 驗證碼框的寬度 --> <attr name="vCodeWidth" format="dimension" /> <!-- 驗證碼框的高度 --> <attr name="vCodeHeight" format="dimension" /> <!-- 驗證碼框間距 --> <attr name="vCodeMargin" format="dimension" /> <!-- 驗證碼預設背景 --> <attr name="vCodeBackgroundNormal" format="reference" /> <!-- 驗證碼焦點背景 --> <attr name="vCodeBackgroundFocus" format="reference" /> </declare-styleable> </resources>
2.用shape畫兩個背景框:
一個預設樣式,一個焦點位置,我寫的框是一樣樣的,就是顏色不一樣。
- 在drawable下畫出兩個即可
?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@color/white"/> <stroke android:color="@color/grey" android:width="1dp"/> </shape>
3.建立自定義View
繼承與RelativeLayout,構造方法選1-3個引數的,順便定義好要用的引數。
public class VerificationCodeView extends RelativeLayout { //輸入的長度 private int vCodeLength = 6; //輸入的內容 private String inputData; private EditText editText; //TextView的list private List<TextView> tvList = new ArrayList<>(); //輸入框預設背景 private int tvBgNormal = R.drawable.verification_code_et_bg_normal; //輸入框焦點背景 private int tvBgFocus = R.drawable.verification_code_et_bg_focus; //輸入框的間距 private int tvMarginRight = 10; //TextView寬 private int tvWidth = 45; //TextView高 private int tvHeight = 45; //TextView字型顏色 private int tvTextColor; //TextView字型大小 private float tvTextSize = 8; 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); } }
4.初始化裡面的TextView
這裡需要一個LinearLayout來作為這些TextView的容器,當然你也可以不用LinearLayout,父佈局用ConstraintLayout一個就能實現。
/** * 設定TextView */ private void initTextView() { LinearLayout linearLayout = new LinearLayout(getContext()); addView(linearLayout); LayoutParams llLayoutParams = (LayoutParams) linearLayout.getLayoutParams(); llLayoutParams.width = LayoutParams.MATCH_PARENT; llLayoutParams.height = LayoutParams.WRAP_CONTENT; //linearLayout.setLayoutParams(llLayoutParams); //水平排列 linearLayout.setOrientation(LinearLayout.HORIZONTAL); //內容居中 linearLayout.setGravity(Gravity.CENTER); for (int i = 0; i < vCodeLength; i++) { TextView textView = new TextView(getContext()); linearLayout.addView(textView); LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) textView.getLayoutParams(); layoutParams.width = tvWidth; layoutParams.height = tvHeight; //只需將中間隔開,所以最後一個textView不需要margin if (i == vCodeLength - 1) { layoutParams.rightMargin = 0; } else { layoutParams.rightMargin = tvMarginRight; } //textView.setLayoutParams(layoutParams); textView.setBackgroundResource(tvBgNormal); textView.setGravity(Gravity.CENTER); textView.setTextSize(tvTextSize); textView.setTextColor(tvTextColor); tvList.add(textView); } }
5.加入EditText
這個EditText設定的跟父容器一樣的大,方便我們點選這個自定義View就能彈起鍵小盤,光閉游標,背景設定空白。
/** * 輸入框和父佈局一樣大,但字型大小0,看不見的 */ private void initEditText() { editText = new EditText(getContext()); addView(editText); LayoutParams layoutParams = (LayoutParams) editText.getLayoutParams(); layoutParams.width = layoutParams.MATCH_PARENT; layoutParams.height = tvHeight; editText.setLayoutParams(layoutParams); //防止橫盤小鍵盤全屏顯示 editText.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN); //隱藏游標 editText.setCursorVisible(false); //最大輸入長度 editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(vCodeLength)}); //輸入型別為數字 editText.setInputType(InputType.TYPE_CLASS_NUMBER); editText.setTextSize(0); editText.setBackgroundResource(0); editText.addTextChangedListener(new 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) { if (s != null && !TextUtils.isEmpty(s.toString())) { //有驗證碼的情況 inputData = s.toString(); //如果是最後一位驗證碼,焦點在最後一個,否者在下一位 if (inputData.length() == vCodeLength) { tvSetFocus(vCodeLength - 1); } else { tvSetFocus(inputData.length()); } //給textView設定資料 for (int i = 0; i < inputData.length(); i++) { tvList.get(i).setText(inputData.substring(i, i + 1)); } for (int i = inputData.length(); i < vCodeLength; i++) { tvList.get(i).setText(""); } } else { //一位驗證碼都沒有的情況 tvSetFocus(0); for (int i = 0; i < vCodeLength; i++) { tvList.get(i).setText(""); } } } @Override public void afterTextChanged(Editable s) { if (s.length() == vCodeLength && null != onVerificationCodeCompleteListener) { onVerificationCodeCompleteListener.verificationCodeComplete(s.toString()); } } }); } /** * 假裝獲取焦點 */ private void tvSetFocus(int index) { tvSetFocus(tvList.get(index)); } private void tvSetFocus(TextView textView) { for (int i = 0; i < vCodeLength; i++) { tvList.get(i).setBackgroundResource(tvBgNormal); } //重新獲取焦點 textView.setBackgroundResource(tvBgFocus); }
6.加上輸入完成回撥
在寫EditText的時候,afterTextChanged里加入回撥。在構造方法里加入初始化的程式碼,就可以了。
public VerificationCodeView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { initTextView(); initEditText(); tvSetFocus(0); } /** * 輸入完成回撥介面 */ public interface OnVerificationCodeCompleteListener { void verificationCodeComplete(String verificationCode); } public void setOnVerificationCodeCompleteListener(OnVerificationCodeCompleteListener onVerificationCodeCompleteListener) { this.onVerificationCodeCompleteListener = onVerificationCodeCompleteListener; }
7.用上自定義的引數
到這執行一下,基本可以顯示出來,但是為了更靈活的使用嘛,我們之前寫的attr就用上了。獲取引數,初始化即可。
<com.wuyanhua.verificationcodeview.VerificationCodeView android:id="@+id/verificationCodeView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingBottom="20dp" android:paddingTop="20dp" app:vCodeBackgroundFocus="@drawable/verification_code_et_bg_focus" app:vCodeBackgroundNormal="@drawable/verification_code_et_bg_normal" app:vCodeDataLength="6" app:vCodeHeight="45dp" app:vCodeMargin="10dp" app:vCodeTextColor="@color/black" app:vCodeTextSize="8sp" app:vCodeWidth="45dp" />
public VerificationCodeView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //獲取自定義樣式的屬性 TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.VerificationCodeView, defStyleAttr, 0); for (int i = 0; i < typedArray.getIndexCount(); i++) { int attr = typedArray.getIndex(i); if (attr == R.styleable.VerificationCodeView_vCodeDataLength) { //驗證碼長度 vCodeLength = typedArray.getInteger(attr, 6); } else if (attr == R.styleable.VerificationCodeView_vCodeTextColor) { //驗證碼字型顏色 tvTextColor = typedArray.getColor(attr, Color.BLACK); } else if (attr == R.styleable.VerificationCodeView_vCodeTextSize) { //驗證碼字型大小 tvTextSize = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 8, getResources().getDisplayMetrics())); } else if (attr == R.styleable.VerificationCodeView_vCodeWidth) { //方框寬度 tvWidth = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 45, getResources().getDisplayMetrics())); } else if (attr == R.styleable.VerificationCodeView_vCodeHeight) { //方框寬度 tvHeight = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,45,getResources().getDisplayMetrics())); }else if(attr == R.styleable.VerificationCodeView_vCodeMargin){ //方框間隔 tvMarginRight = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,10,getResources().getDisplayMetrics())); }else if(attr == R.styleable.VerificationCodeView_vCodeBackgroundNormal){ //預設背景 tvBgNormal = typedArray.getResourceId(attr,R.drawable.verification_code_et_bg_normal); }else if(attr == R.styleable.VerificationCodeView_vCodeBackgroundFocus){ //焦點背景 tvBgFocus = typedArray.getResourceId(attr,R.drawable.verification_code_et_bg_focus); } } //用完回收 typedArray.recycle(); init(); }