自定義驗(yàn)證碼數(shù)字鍵盤
序言
我們會(huì)遇到很多輸入六位數(shù),四為數(shù)的驗(yàn)證碼界面
@[toc]
最終效果如下

在這里插入圖片描述
問題
那我們做這類界面的時(shí)候遇到的什么問題呢
- 需要有四個(gè)或者六個(gè)固定的輸入框供用戶輸入
- 輸入框內(nèi)不能有光標(biāo),用戶只能從后往前刪除數(shù)字
- 如果用edittext那么會(huì)一直有光標(biāo)可以選擇,文字輸入一個(gè)之后跳轉(zhuǎn)到另一個(gè)會(huì)有一定的bug,包括edittext的聚焦,光標(biāo)顯示在不對(duì)的輸入框內(nèi)的問題等等
- 如果用原生的鍵盤,那么有的時(shí)候不可避免的用戶可以切換到標(biāo)點(diǎn)符號(hào)輸入鍵盤,有的甚至可以到英文鍵盤,這樣需要過濾掉這些不能輸入的字符。而且用戶切換過去之后不好切回來體驗(yàn)極差
- 輸入框的喚醒,我們很難很好的去操作輸入框的顯示和隱藏,這是很蛋疼的代碼,可能你要寫很多代碼才能去隱藏掉原生的輸入框,然后在有些機(jī)型上可能這個(gè)代碼并沒什么效果。
預(yù)期操作方式
那我們預(yù)期達(dá)到的操作方式是什么呢,我們假設(shè)用戶是傻子或者好奇心極強(qiáng),以及故意找漏洞的不懷好意的人。
- 我們希望用戶只能點(diǎn)擊,甚至只能看到數(shù)字鍵盤,連一個(gè)標(biāo)點(diǎn)符號(hào)按鈕都看不到
- 我們希望用戶不要去隨意的換輸入框去輸入數(shù)字,只能從前往后輸入,只能從后往前刪除
這樣的輸入框可能很簡(jiǎn)單很呆板,但是體驗(yàn)非常好,也避免了不懷好意的用戶用出很多問題,因?yàn)槲覀兊墓δ芫瓦@么多,功能越多問題越多,那么長(zhǎng)痛不如短痛,我們最簡(jiǎn)單的辦法就是學(xué)習(xí)市面上大多數(shù)廠家那樣,自定義這整套界面。
實(shí)現(xiàn)
那我么實(shí)現(xiàn)就分為兩個(gè)部分,一個(gè)是輸入框界面,還有一個(gè)就是自定義的鍵盤。
輸入框界面思路
- 我們根本不需要光標(biāo),這會(huì)影響我們的原生輸入框的彈出和影藏,還會(huì)不好控制焦點(diǎn),那么我們就只需要用TextView來代替
- 我們只需要遍歷來判斷輸入框中有沒有字,就可以做到按順序輸入的視覺效果
- 輸入到最后一個(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)證碼輸入框的頁面邏輯了。