Android 自定義View_方框驗證碼

Android 自定義View之方框驗證碼

Gihub項目

  • 最近項目有一個需求,6個方框的驗證碼,要求不能出現光標,然后刪除鍵一個個刪除,輸入完成會回調。


    效果圖.gif

  • 剛開始做這個自定義view,我是想著用多個EditText來實現功能,做到后面發現在獲取焦點這個問題上,多個EditText處理不了,于是上網看了別的思路,就是用多個TextView顯示,但是輸入的EditText只有一個,我覺得這個思路可行,自己再次動手改代碼。

  • 具體:這個View就是繼承與RelativeLayout,然后里面有一個LinearLayout,水平排列放下6個TextView,最后在LinearLayout上蓋上一個字體大小為0的EditText。
    EditText做監聽,將內容賦值給每一個TextView。


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(TypedValue.COMPLEX_UNIT_PX,tvTextSize);
        textView.setTextColor(tvTextColor);

        tvList.add(textView);
    }
}

5.加入EditText

這個EditText設置的跟父容器一樣的大,方便我們點擊這個自定義View就能彈起鍵小盤,光閉光標,背景設置空白。每一位數字寫下后,就將下一位的TextView設為焦點。

/**
 * 輸入框和父布局一樣大,但字體大小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 (null != onVerificationCodeCompleteListener) {
                if (s.length() == vCodeLength) {
                    onVerificationCodeCompleteListener.verificationCodeComplete(s.toString());
                } else {
                    onVerificationCodeCompleteListener.verificationCodeIncomplete(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();
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,666評論 25 708
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,792評論 2 59
  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數據庫組件 SD...
    陽明先生_X自主閱讀 16,000評論 3 119
  • 無論我和他們討論什么 他們都和我談錢 他們讓我鄙視 我讓他們可憐 我以為有些快樂 在于內心深處 我以為有些生活 超...
    更向遠行閱讀 140評論 0 1
  • 愿做矗立的燈塔一座, 因為曾經被這樣的光明指引; 愿做熾熱的爐火一團, 因為曾經被這樣的光熱溫暖; 愿做肥沃的土地...
    塵光閱讀 368評論 3 4