自定義驗(yàn)證碼數(shù)字鍵盤

自定義驗(yàn)證碼數(shù)字鍵盤

序言

我們會(huì)遇到很多輸入六位數(shù),四為數(shù)的驗(yàn)證碼界面
@[toc]

最終效果如下

在這里插入圖片描述

問題

那我們做這類界面的時(shí)候遇到的什么問題呢

  1. 需要有四個(gè)或者六個(gè)固定的輸入框供用戶輸入
  2. 輸入框內(nèi)不能有光標(biāo),用戶只能從后往前刪除數(shù)字
  3. 如果用edittext那么會(huì)一直有光標(biāo)可以選擇,文字輸入一個(gè)之后跳轉(zhuǎn)到另一個(gè)會(huì)有一定的bug,包括edittext的聚焦,光標(biāo)顯示在不對(duì)的輸入框內(nèi)的問題等等
  4. 如果用原生的鍵盤,那么有的時(shí)候不可避免的用戶可以切換到標(biāo)點(diǎn)符號(hào)輸入鍵盤,有的甚至可以到英文鍵盤,這樣需要過濾掉這些不能輸入的字符。而且用戶切換過去之后不好切回來體驗(yàn)極差
  5. 輸入框的喚醒,我們很難很好的去操作輸入框的顯示和隱藏,這是很蛋疼的代碼,可能你要寫很多代碼才能去隱藏掉原生的輸入框,然后在有些機(jī)型上可能這個(gè)代碼并沒什么效果。

預(yù)期操作方式

那我們預(yù)期達(dá)到的操作方式是什么呢,我們假設(shè)用戶是傻子或者好奇心極強(qiáng),以及故意找漏洞的不懷好意的人。

  1. 我們希望用戶只能點(diǎn)擊,甚至只能看到數(shù)字鍵盤,連一個(gè)標(biāo)點(diǎn)符號(hào)按鈕都看不到
  2. 我們希望用戶不要去隨意的換輸入框去輸入數(shù)字,只能從前往后輸入,只能從后往前刪除

這樣的輸入框可能很簡(jiǎn)單很呆板,但是體驗(yàn)非常好,也避免了不懷好意的用戶用出很多問題,因?yàn)槲覀兊墓δ芫瓦@么多,功能越多問題越多,那么長(zhǎng)痛不如短痛,我們最簡(jiǎn)單的辦法就是學(xué)習(xí)市面上大多數(shù)廠家那樣,自定義這整套界面。

實(shí)現(xiàn)

那我么實(shí)現(xiàn)就分為兩個(gè)部分,一個(gè)是輸入框界面,還有一個(gè)就是自定義的鍵盤。

輸入框界面思路

  1. 我們根本不需要光標(biāo),這會(huì)影響我們的原生輸入框的彈出和影藏,還會(huì)不好控制焦點(diǎn),那么我們就只需要用TextView來代替
  2. 我們只需要遍歷來判斷輸入框中有沒有字,就可以做到按順序輸入的視覺效果
  3. 輸入到最后一個(gè)的時(shí)候可以自動(dòng)觸發(fā)發(fā)送驗(yàn)證碼的操作

鍵盤界面的思路

1.我們只需要數(shù)字0-9這十個(gè)數(shù)字的輸入按鈕,以及一個(gè)刪除按鈕
2.我們需要可以彈出隱藏這個(gè)鍵盤,那么底部彈出用popuwindow再好不過了

思路總結(jié)

以上便是我提供的所有思路,按照上述描述大部分熟練的人應(yīng)該可以自己寫出來了。

以下貼出部分代碼方便大家借鑒

輸入框代碼

xml部分

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="70dp"
        android:text="請(qǐng)輸入6位驗(yàn)證碼"
        android:textColor="@color/common_text_color_666" />

    <LinearLayout
        android:id="@+id/input_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:gravity="center">

        <TextView
            android:id="@+id/input1"
            style="@style/TeamInputStyle" />

        <TextView
            android:id="@+id/input2"
            style="@style/TeamInputStyle" />

        <TextView
            android:id="@+id/input3"
            style="@style/TeamInputStyle" />

        <TextView
            android:id="@+id/input4"
            style="@style/TeamInputStyle" />

        <TextView
            android:id="@+id/input5"
            style="@style/TeamInputStyle" />

        <TextView
            android:id="@+id/input6"
            style="@style/TeamInputStyle"
            android:layout_marginEnd="0dp" />
    </LinearLayout>


</LinearLayout>

style

    <style name="TeamInputStyle">
        <item name="android:layout_width">50dp</item>
        <item name="android:layout_height">50dp</item>
        <item name="android:textSize">30sp</item>
        <item name="android:textStyle">bold</item>
        <item name="android:textColor">@color/textBlack</item>
        <item name="android:gravity">center</item>
        <item name="android:layout_marginEnd">10dp</item>
        <item name="android:background">@drawable/team_number</item>
    </style>

drawble

//正常
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
    <corners android:radius="8dp" />
    <solid android:color="#ffffff" />
    <stroke
        android:width="1dp"
        android:color="#AAA" />
</shape>
//紅色
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
    <solid android:color="#ffffff" />
    <corners android:radius="8dp" />
    <stroke
        android:width="1dp"
        android:color="#FF5742" />
</shape>
//灰色
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#ffffff" />
    <corners android:radius="8dp" />
    <stroke
        android:width="1dp"
        android:color="#CC000000" />
</shape>

kotlin代碼

    override fun afterTextChanged(s: Editable?) {
        if (s?.length == 1) {//這里只監(jiān)聽了最后一個(gè)輸入框
            val builder = StringBuilder()
            inputs.forEach {
                builder.append(it.text)
            }
            val params = mutableMapOf(
                    Constant.PASS_KEY to builder.toString()
            )
            //然后就可以把這個(gè)params拿去做請(qǐng)求了
        }
    }

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    }

    override fun onInput(text: String) {

        for (input in inputs) {
            if (input.text.isEmpty()) {
                input.text = text
                if (input.id == R.id.input1) {
                    inputs.forEach {
                        it.setBackgroundResource(R.drawable.team_number)
                    }
                }
                input.setBackgroundResource(R.drawable.team_number_gray)
                return
            }
        }
    }

    override fun delete() {
        for (input in inputs.asReversed()) {
            if (input.text.isNotEmpty()) {
                input.text = ""
                return
            }
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_xxx)
        inputPop = TeamInputPopupWindow(this, this)
        title_text.text = "輸入驗(yàn)證碼"
        inputs = listOf(
                input1,
                input2,
                input3,
                input4,
                input5,
                input6
        )
        input6.addTextChangedListener(this)
        input6.post {
            inputPop.showPop()
        }
    }

以下是TeamInputPopupWindow代碼

class TeamInputPopupWindow(private val activity: Activity, private val listener: OnInputListener) :
        View.OnClickListener {


    private lateinit var mPopupWindow: PopupWindow

    fun showPop() {
        val view = LayoutInflater.from(activity)
                .inflate(R.layout.team_input, activity.window.decorView as ViewGroup, false)
        //設(shè)置view
        setView(view)
        mPopupWindow = PopupWindow(
                view,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        )
        mPopupWindow.isFocusable = true
        mPopupWindow.isOutsideTouchable = true
        mPopupWindow.showAtLocation(view, Gravity.BOTTOM, 0, 0)
//        mPopupWindow.showAtLocation(parent, Gravity.BOTTOM, 0, 0)
    }

    fun dismiss() {
        mPopupWindow.dismiss()
    }

    private fun setView(view: View) {
        view.findViewById<View>(R.id.one).setOnClickListener(this)
        view.findViewById<View>(R.id.two).setOnClickListener(this)
        view.findViewById<View>(R.id.three).setOnClickListener(this)
        view.findViewById<View>(R.id.four).setOnClickListener(this)
        view.findViewById<View>(R.id.five).setOnClickListener(this)
        view.findViewById<View>(R.id.six).setOnClickListener(this)
        view.findViewById<View>(R.id.seven).setOnClickListener(this)
        view.findViewById<View>(R.id.eight).setOnClickListener(this)
        view.findViewById<View>(R.id.nine).setOnClickListener(this)
        view.findViewById<View>(R.id.zero).setOnClickListener(this)
        view.findViewById<View>(R.id.delete).setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.one -> listener.onInput("1")
            R.id.two -> listener.onInput("2")
            R.id.three -> listener.onInput("3")
            R.id.four -> listener.onInput("4")
            R.id.five -> listener.onInput("5")
            R.id.six -> listener.onInput("6")
            R.id.seven -> listener.onInput("7")
            R.id.eight -> listener.onInput("8")
            R.id.nine -> listener.onInput("9")
            R.id.zero -> listener.onInput("0")
            R.id.delete -> listener.delete()
        }
    }
}

interface OnInputListener {
    fun onInput(text: String)
    fun delete()
}

以上便是大部分的片段代碼了,根據(jù)以上邏輯已經(jīng)可以完成大部分的效果了。

總結(jié)

很多東西其實(shí)思路比實(shí)現(xiàn)更重要,如果你知道怎么實(shí)現(xiàn)會(huì)少坑的話,盡量在設(shè)計(jì)階段就回避掉這些東西。以上便是如何實(shí)現(xiàn)驗(yàn)證碼輸入框的頁面邏輯了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ??JavaScript 與 HTML 之間的交互是通過事件實(shí)現(xiàn)的。 ??事件,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,690評(píng)論 1 11
  • (本文來自《Custom Keyboard》)自定義鍵盤為那些希望體驗(yàn)更新穎的輸入法或者需要用到iOS不支持的語言...
    RickMao閱讀 9,245評(píng)論 3 65
  • 不知不覺,歲寒輸入法的更新歷史已經(jīng)可以列出這么一長(zhǎng)串來了。從中可以看出,歲寒的發(fā)展過程也是一個(gè)不斷試錯(cuò)的過程,其中...
    臨歲之寒閱讀 34,873評(píng)論 1 6
  • 吃貨地圖產(chǎn)品需求文檔 V1.0-2015/03/30 1概述 1.1產(chǎn)品概述及目標(biāo) 概述:“吃貨地圖”是一款基于i...
    michaelshan閱讀 5,984評(píng)論 1 46
  • 界面是軟件與用戶交互的最直接的層,界面的好壞決定用戶對(duì)軟件的第一印象。而且設(shè)計(jì)良好的界面能夠引導(dǎo)用戶自己完成...
    A夢(mèng)想才讓心跳存在閱讀 1,134評(píng)論 0 4

友情鏈接更多精彩內(nèi)容