Android自定義控件:通用驗(yàn)證碼輸入框

關(guān)于自定義控件的基礎(chǔ)知識(shí)
-- 可自行查詢資料据途。
-- 也可關(guān)注我們岸梨,后期介紹自定義統(tǒng)計(jì)圖表時(shí),會(huì)統(tǒng)一介紹自定義控件的常用知識(shí)殉摔。


需求

4位驗(yàn)證碼輸入框:
效果圖:


4位驗(yàn)證碼輸入框.gif
1. 輸入框一行可輸入4位數(shù)字類型的驗(yàn)證碼唯咬;
2. 4位數(shù)字之間有間隔(包括底線)秃殉;
3. 輸入框不允許有光標(biāo)姥份;
4. 底線根據(jù)輸入位置顯示高亮(藍(lán)色)树埠;
6. 輸入完成祠丝,回調(diào)結(jié)果疾呻,輸入過程中,也進(jìn)行回調(diào)写半;

分析

這種效果罐韩,很難直接在Edittext上處理:
-- 輸入框均分4等份,還要有間隔污朽;
-- 更難處理的是Edittext輸入框禁止光標(biāo)散吵,那么,沒有光標(biāo),我們?nèi)绾握{(diào)起虛擬鍵盤輸入數(shù)據(jù)矾睦?
-- 等...

與其在一個(gè)控件上折騰晦款,這么難受,不如自定義一個(gè)控件枚冗,實(shí)現(xiàn)這種效果缓溅。
自定義控件最簡單的方案:使用多個(gè)控件,組合出這種效果赁温。

  1. 布局如何實(shí)現(xiàn)坛怪?
1.禁止光標(biāo),我們直接使用TextView就解決了股囊,而非Edittext;
2.一行顯示4位數(shù)字袜匿,比較簡單,可以使用線性布局的權(quán)重稚疹,對(duì)TextView進(jìn)行控制為4等分居灯;
3.每個(gè)TextView下面跟著一個(gè)底線,將來我們就能對(duì)底線設(shè)置高亮顏色了内狗;

這樣怪嫌,基本的布局展示就可以了!A场岩灭!

  1. 使用了TextView,那么我們?nèi)绾谓邮沼脩舻妮斎肽兀?/li>
也很簡單赂鲤,我們?cè)?個(gè)TextView的上方平鋪一個(gè)EditText川背,設(shè)置透明,
當(dāng)用戶點(diǎn)擊到該控件時(shí)蛤袒,會(huì)自動(dòng)調(diào)起軟鍵盤熄云,接收輸入的文本。
  1. EditText接收到用戶輸入的文本妙真,如何顯示在TextView呢缴允?
我們監(jiān)聽EditText文本輸入事件,最多僅接收4個(gè)輸入字符珍德,
每接收到一個(gè)字符练般,我們就賦值給對(duì)應(yīng)的TextView;
底線也隨要設(shè)置的文本切換顯示高亮锈候;
  1. 如何刪除已輸入的數(shù)值薄料?
我們監(jiān)聽EditText按鍵事件,攔截DEL鍵泵琳,從后向前挨著刪除字符即可摄职;
底線也隨要?jiǎng)h除的文本切換顯示高亮誊役;
  1. 是否需要自定義屬性
分析我們自己的項(xiàng)目,雖然是公用的控件谷市,但是該控件比較簡單蛔垢,沒有特別的要求,所以沒必要自定義屬性了迫悠!
如果大家有需要的鹏漆,可根據(jù)需要自己定義;
如何定義屬性创泄?請(qǐng)自行查找資料艺玲;

既然,問題都分析清楚了鞠抑,那我們就開始快速實(shí)現(xiàn)吧


具體實(shí)現(xiàn)

  1. 布局文件 phone_code.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <LinearLayout
        android:id="@+id/ll_code"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical"
            android:layout_marginRight="7dp">
            <TextView
                android:id="@+id/tv_code1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="#2D2D2D"
                android:textSize="40sp"
                android:background="@null"
                android:gravity="center"/>
            <View
                android:id="@+id/v1"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#3F8EED" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical"
            android:layout_marginRight="7dp"
            android:layout_marginLeft="7dp">
            <TextView
                android:id="@+id/tv_code2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="#2D2D2D"
                android:textSize="40sp"
                android:background="@null"
                android:gravity="center"/>
            <View
                android:id="@+id/v2"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#999999" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical"
            android:layout_marginRight="7dp"
            android:layout_marginLeft="7dp">
            <TextView
                android:id="@+id/tv_code3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="#2D2D2D"
                android:textSize="40sp"
                android:background="@null"
                android:gravity="center"/>
            <View
                android:id="@+id/v3"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#999999" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical"
            android:layout_marginLeft="7dp">
            <TextView
                android:id="@+id/tv_code4"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="#2D2D2D"
                android:background="@null"
                android:textSize="40sp"
                android:gravity="center"/>
            <View
                android:id="@+id/v4"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#999999" />
        </LinearLayout>
    </LinearLayout>

    <EditText
        android:id="@+id/et_code"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/ll_code"
        android:layout_alignBottom="@+id/ll_code"
        android:background="@android:color/transparent"
        android:textColor="@android:color/transparent"
        android:cursorVisible="false"
        android:inputType="number"/>
</RelativeLayout>

et_code 輸入框饭聚,設(shè)置了透明和無光標(biāo),僅接收數(shù)字碍拆;
tv_code1~4 為顯示數(shù)字的控件若治;
v1~4 為數(shù)字文本的底線慨蓝,用于設(shè)置高亮感混;

  1. 自定義控件代碼 PhoneCode
package iwangzhe.customview2.phonecode;

import android.content.Context;
import android.graphics.Color;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

import iwangzhe.customview2.R;

/**
 * 類:PhoneCode
 * 作者: qxc
 * 日期:2018/3/14.
 */
public class PhoneCode extends RelativeLayout {
    private Context context;
    private TextView tv_code1;
    private TextView tv_code2;
    private TextView tv_code3;
    private TextView tv_code4;
    private View v1;
    private View v2;
    private View v3;
    private View v4;
    private EditText et_code;
    private List<String> codes = new ArrayList<>();
    private InputMethodManager imm;

    public PhoneCode(Context context) {
        super(context);
        this.context = context;
        loadView();
    }

    public PhoneCode(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        loadView();
    }

    private void loadView(){
        imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        View view = LayoutInflater.from(context).inflate(R.layout.phone_code, this);
        initView(view);
        initEvent();
    }

    private void initView(View view){
        tv_code1 = (TextView) view.findViewById(R.id.tv_code1);
        tv_code2 = (TextView) view.findViewById(R.id.tv_code2);
        tv_code3 = (TextView) view.findViewById(R.id.tv_code3);
        tv_code4 = (TextView) view.findViewById(R.id.tv_code4);
        et_code = (EditText) view.findViewById(R.id.et_code);
        v1 = view.findViewById(R.id.v1);
        v2 = view.findViewById(R.id.v2);
        v3 = view.findViewById(R.id.v3);
        v4 = view.findViewById(R.id.v4);
    }

    private void initEvent(){
        //驗(yàn)證碼輸入
        et_code.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }
            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }
            @Override
            public void afterTextChanged(Editable editable) {
                if(editable != null && editable.length()>0) {
                    et_code.setText("");
                    if(codes.size() < 4){
                        codes.add(editable.toString());
                        showCode();
                    }
                }
            }
        });
        // 監(jiān)聽驗(yàn)證碼刪除按鍵
        et_code.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
                if (keyCode == KeyEvent.KEYCODE_DEL && keyEvent.getAction() == KeyEvent.ACTION_DOWN && codes.size()>0) {
                    codes.remove(codes.size()-1);
                    showCode();
                    return true;
                }
                return false;
            }
        });
    }

    /**
     * 顯示輸入的驗(yàn)證碼
     */
    private void showCode(){
        String code1 = "";
        String code2 = "";
        String code3 = "";
        String code4 = "";
        if(codes.size()>=1){
            code1 = codes.get(0);
        }
        if(codes.size()>=2){
            code2 = codes.get(1);
        }
        if(codes.size()>=3){
            code3 = codes.get(2);
        }
        if(codes.size()>=4){
            code4 = codes.get(3);
        }
        tv_code1.setText(code1);
        tv_code2.setText(code2);
        tv_code3.setText(code3);
        tv_code4.setText(code4);        
        
        setColor();//設(shè)置高亮顏色
        callBack();//回調(diào)
    }

    /**
     * 設(shè)置高亮顏色
     */
    private void setColor(){
        int color_default = Color.parseColor("#999999");
        int color_focus = Color.parseColor("#3F8EED");
        v1.setBackgroundColor(color_default);
        v2.setBackgroundColor(color_default);
        v3.setBackgroundColor(color_default);
        v4.setBackgroundColor(color_default);
        if(codes.size()==0){
            v1.setBackgroundColor(color_focus);
        }
        if(codes.size()==1){
            v2.setBackgroundColor(color_focus);
        }
        if(codes.size()==2){
            v3.setBackgroundColor(color_focus);
        }
        if(codes.size()>=3){
            v4.setBackgroundColor(color_focus);
        }
    }

    /**
     * 回調(diào)
     */
    private void callBack(){
        if(onInputListener==null){
            return;
        }
        if(codes.size()==4){
            onInputListener.onSucess(getPhoneCode());
        }else{
            onInputListener.onInput();
        }
    }

    //定義回調(diào)
    public interface OnInputListener{
        void onSucess(String code);
        void onInput();
    }
    private OnInputListener onInputListener;
    public void setOnInputListener(OnInputListener onInputListener){
        this.onInputListener = onInputListener;
    }

    /**
     * 顯示鍵盤
     */
    public void showSoftInput(){
        //顯示軟鍵盤
        if(imm!=null && et_code!=null) {
            et_code.postDelayed(new Runnable() {
                @Override
                public void run() {
                    imm.showSoftInput(et_code, 0);
                }
            },200);
        }
    }

    /**
     * 獲得手機(jī)號(hào)驗(yàn)證碼
     * @return 驗(yàn)證碼
     */
    public String getPhoneCode(){
        StringBuilder sb = new StringBuilder();
        for (String code : codes) {
            sb.append(code);
        }
        return sb.toString();
    }
}

codes 集合,用于存放用戶輸入的所有數(shù)字礼烈。使用該集合弧满,可簡化輸入框、文本關(guān)聯(lián)邏輯和事件之間處理此熬;
showSoftInput方法:顯示輸入鍵盤庭呜,可被外界調(diào)用;
getPhoneCode方法:獲得用戶輸入的驗(yàn)證碼犀忱,可被外界調(diào)用募谎;
OnInputListener接口:定義的數(shù)值輸入回調(diào),用于告訴調(diào)用者是輸入中阴汇,還是輸入完成数冬;
(OnInputListener用途舉例:在實(shí)際項(xiàng)目中,當(dāng)輸入完成搀庶,底部【下一步】或【確定】按鈕變成可點(diǎn)擊拐纱,否則,變?yōu)椴豢牲c(diǎn)擊哥倔。)

  1. 調(diào)用者 MainActivity
    布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="iwangzhe.customview2.MainActivity">
    <iwangzhe.customview2.phonecode.PhoneCode
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pc_1"
        android:layout_below="@+id/fpc_1"
        android:layout_marginTop="40dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"/>
</RelativeLayout>

代碼

package iwangzhe.customview2;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import iwangzhe.customview2.phonecode.PhoneCode;
public class MainActivity extends AppCompatActivity {
    PhoneCode pc_1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        pc_1 = (PhoneCode) findViewById(R.id.pc_1);
        //注冊(cè)事件回調(diào)(根據(jù)實(shí)際需要秸架,可寫,可不寫)
        pc_1.setOnInputListener(new PhoneCode.OnInputListener() {
            @Override
            public void onSucess(String code) {
                //TODO: 例如底部【下一步】按鈕可點(diǎn)擊
            }

            @Override
            public void onInput() {
                //TODO:例如底部【下一步】按鈕不可點(diǎn)擊
            }
        });
    }

    private void test(){
        //獲得驗(yàn)證碼
        String phoneCode = pc_1.getPhoneCode();
        //......
        //......
        //更多操作
    }
}


總結(jié):
此控件實(shí)現(xiàn)起來咆蒿,很簡單东抹,代碼量也非常少蚂子。
本文章,主要是為了讓大家了解自定義控件的過程府阀,如果想在自己的項(xiàng)目中使用缆镣,請(qǐng)根據(jù)需要自行調(diào)整優(yōu)化。

Demo下載地址:
(為了減小Demo大小试浙,我刪除了build下的文件董瞻,大家獲取后rebuild一下代碼,就可以了)
https://pan.baidu.com/s/1OAcUdmwC_wFOrkontveAeA

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末田巴,一起剝皮案震驚了整個(gè)濱河市钠糊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壹哺,老刑警劉巖抄伍,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異管宵,居然都是意外死亡截珍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門箩朴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岗喉,“玉大人,你說我怎么就攤上這事炸庞∏玻” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵埠居,是天一觀的道長查牌。 經(jīng)常有香客問我,道長滥壕,這世上最難降的妖魔是什么纸颜? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮绎橘,結(jié)果婚禮上胁孙,老公的妹妹穿的比我還像新娘。我一直安慰自己金踪,他們只是感情好浊洞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胡岔,像睡著了一般法希。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上靶瘸,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天苫亦,我揣著相機(jī)與錄音毛肋,去河邊找鬼。 笑死屋剑,一個(gè)胖子當(dāng)著我的面吹牛润匙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播唉匾,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼孕讳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了巍膘?” 一聲冷哼從身側(cè)響起厂财,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎峡懈,沒想到半個(gè)月后璃饱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肪康,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年荚恶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磷支。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谒撼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出齐唆,到底是詐尸還是另有隱情嗤栓,我是刑警寧澤冻河,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布箍邮,位于F島的核電站,受9級(jí)特大地震影響叨叙,放射性物質(zhì)發(fā)生泄漏锭弊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一擂错、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剑鞍,春花似錦、人聲如沸哪痰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惋啃。三九已至边灭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惰帽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國打工莱衩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留睹晒,地道東北人伪很。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓辱匿,卻偏偏與公主長得像絮短,于是被迫代替她去往敵國和親丁频。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叔磷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,167評(píng)論 25 707
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 6,424評(píng)論 0 17
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 32,940評(píng)論 6 472
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,766評(píng)論 22 665
  • 關(guān)于工作的思考:公司上市的IPO文件需要翻譯饰恕,涉及很多語種挠羔。券商和律師要求翻譯成中文,提供翻譯公司的資質(zhì)和譯員的資...
    雅燕zyy閱讀 342評(píng)論 0 0