Android自定義密碼輸入框的簡單實現過程
一、實現效果及方案
預期效果圖:

如上圖所示,要實現一個這種密碼輸入框的樣式,原生并未提供類似的效果,所以需要自定義控件的方式實現。
預期的基礎效果:
只接受數字;
支持輸入加密顯示;
支持刪除;
密碼位數可配置;
文字大小、顏色、數字框背景可配置;
方案分析:
需要解決的問題:
配置性;
輸入、刪除如何實現?
整體UI如何實現?
1.對于輸入刪除可以通過setOnKeyListener監(jiān)聽軟件盤的事件。
2.可配置性數據可以通過自定義的屬性文件配置;
3.對于UI效果:
A:可以基于原生控件做開發(fā),每一個數字布局對應一個TextView,選用數據結構對其管理,再選用一種容器布局,比如LinearLayout進行添加。
B:通過自定義View的方式開發(fā),需要自行繪制,繪制的內容包括背景、及密碼內容、密碼加密樣式內容。
二、實現
這里選用方案B的方式進行實現,盡量使用較少的控件去實現,使用A的方案至少要用到5個原生控件的組合。
1.繼承ViewGrop還是View?
如果選用方案A的話其實算是繼承了ViewGrop,而內部的單個數字則作為一個獨立的子控件,這樣的話是可以繼承ViewGrop的
,但顯然不需要這么麻煩(需要處理layout等),這個密碼輸入就是一個獨立的控件不需要再加入子控件,所以直接繼承View。
class PasswordEditText @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
) : View(context, attributeSet, 0) {
}
2.繼承View的話就要處理 wrap_content,所以要重寫onMeasure,即在未設置具體的寬度時也要能夠正常的顯示測量。先定義一些寬高顏色的變量:
//密碼位數
private var passwordLength = 4
private var textColor = 0
//間隔 -> dp2px
private var itemPadding = 5
//單個數字包括背景寬度 dp2px
private var itemWidth = 30
private var bgItemColor = 0
private val mPaintBg = Paint()
//用于存儲輸入后的密碼
private val password = arrayOfNulls<String>(passwordLength)
寬度的話相對來說還是很好計算的:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
var width = 0
when (MeasureSpec.getMode(widthMeasureSpec)) {
MeasureSpec.UNSPECIFIED,MeasureSpec.AT_MOST ->{
width = itemWidth * passwordLength + itemPadding * (passwordLength-1)
}
MeasureSpec.EXACTLY ->{
width = MeasureSpec.getSize(widthMeasureSpec)
itemWidth = (width - itemPadding *(passwordLength -1)) / passwordLength
}
}
setMeasuredDimension(width,itemWidth)
}
看著UI圖基本可以算出來了,不涉及太復雜的計算,這里并未對高度進行處理,理論上高度的值應該用指定的就好了;
需要做的測量基本就是這些了,下面開始繪制背景了:
也很簡單根據 passwordLength 循環(huán)繪制圓角矩形就OK了,而參數的話也很好計算出來:
private fun drawBgItems(canvas: Canvas) {
for (i in password.indices) {
未處理padding值 加上即可
val rect = RectF(
(i * itemWidth + (i) * itemPadding).toFloat(),
0f,
((i+1) * itemWidth + i * itemPadding).toFloat(),
itemWidth.toFloat()
)
canvas.drawRoundRect(rect, 5f, 5f, mPaintBg)
}
}
為了讓效果更明顯,先畫了一個顏色鮮艷的:

背景的話就繪制OK了,下面要做的就是監(jiān)聽事件再繪制密碼內容了;
要實現一個OnKeyListener
//鍵盤監(jiān)聽
private val keyListener = OnKeyListener { v, keyCode, event ->
val action = event.action
if (action == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_DEL) {
//刪除
return@OnKeyListener true
}
if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
//數字鍵
}
if (keyCode == KeyEvent.KEYCODE_ENTER) {
//確認鍵
return@OnKeyListener true
}
}
return@OnKeyListener false
}
這里只是添加好了回調條件,還要做相應的處理。但鍵盤還沒彈出,所以要先處理點擊事件調用系統(tǒng)的方法主動彈出軟鍵盤才行
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event!!.action == MotionEvent.ACTION_DOWN) {
//獲取焦點
requestFocus()
//getContext().getSystemService(Context.INPUT_METHOD_SERVICE)
inputManager.showSoftInput(this, InputMethodManager.SHOW_FORCED)
return true
}
return super.onTouchEvent(event)
}
重寫onTouchEvent之后就可以攔截點擊事件彈出鍵盤拉。而View和軟鍵盤的聯系需要通過onCreateInputConnection 來實現,具體可以看下源碼的介紹。
下面接著處理監(jiān)聽事件,首先是在接受到數字輸入時的處理,要把輸入的數字存儲到容器并繪制出來
這里需要注意keyCode 的值,看下源碼并不是KEYCODE_0 就是0了

再存儲一下輸入的數字:
if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
//數字鍵
password[currentInputPosition] = (keyCode - 7).toString()
currentInputPosition++
postInvalidate()
return@OnKeyListener true
}
currentInputPosition為了標記當前操作的位置,方便添加和刪除,因為都是從兩頭開始的用這個值就可以了。
下面開始繪制文字了,如果加密的話只需要在每個背景的中心畫一個小黑點就OK了,或者直接畫一個數字,根據基線用drawText畫就好了,而Y軸的基線很好確定就是高度的一半像素減去高度文字一半,通過設置textAlign = Paint.Align.CENTER即可實現橫向上的居中(會根據X基線),X軸的基線則需要計算一下,比如第一個框的X基線則應該是,框寬的一半再減去繪制文字寬度的一半,這樣才能在中間,第二個框內X的基線應該是:paddingLeft+1框寬+1padding+框寬/2
所以繪制文字的代碼就出來了:
//繪制文字
private fun drawPasswordNumber(canvas: Canvas) {
for (i in password.indices) {
if (password[i] != null) {
//沒有開啟明文顯示,繪制密碼密文
val txt = if (isCipherEnable) cipherString else password[i]
mPaintTv.getTextBounds(txt, 0, txt!!.length, rectTv)
val offset = (rectTv.top + rectTv.bottom) / 2
canvas.drawText(
password[i]!!,
(paddingLeft + itemWidth * i + itemPadding * i + itemWidth / 2).toFloat(),
(paddingTop + itemWidth / 2).toFloat() - offset, mPaintTv
)
}
}
}
這里加了是否開啟密文顯示的開關,最終運行的效果如下:


這個黑點也可以通過畫圓的方式進行繪制,但無法通過字符串進行動態(tài)配置。
再輸入完四位以后在點的話就會數組越界閃退了,所以在完成相應位數的添加后要禁止再繪制。
if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
//加入判斷 currentInputPosition
if (currentInputPosition == passwordLength) {
return@OnKeyListener true
}
password[currentInputPosition] = (keyCode - 7).toString()
currentInputPosition++
postInvalidate()
return@OnKeyListener true
}
下面要處理刪除操作了,刪除要做的就是去除數組中保存的已輸入密碼,更新操作標記位,再刷新繪制就OK了
if (keyCode == KeyEvent.KEYCODE_DEL) {
//刪除
if(currentInputPosition == 0){
return@OnKeyListener true
}
password[currentInputPosition-1] = null
currentInputPosition--
postInvalidate()
return@OnKeyListener true
}
看下最后的UI效果:

下面可以提供一些對外的方法、接口,比如獲取內容,輸入刪除確認的回調監(jiān)聽。
//獲取輸入內容
fun getTextContent():String {
val sb = StringBuilder()
for (p in password) {
p?.let {
sb.append(p)
}
}
return sb.toString()
}
//操作回調 加些需要的參數
interface OperationListener{
fun inputOperationCallBack()
fun completeOperationCallBack()
fun deleteOperationCallBack()
}
這里還可以繼續(xù)開發(fā)其他一些主流的樣式,比如下劃線、網格的樣式,但繪制思路基本相同,還可以加上一個任務類執(zhí)行繪制光標的操作。
總結
一個簡單的自定義View Demo,自定義View的難點在于參數的計算和很多API一不用就會忘記,但是繼承ViewGroup還是View,測量繪制布局的過程以及基本的繪制方法配置還是要清楚些,或者說能想起來,對于太復雜難以開發(fā)的控件感覺如果有現成的還是可以直接用的,畢竟沒那么多時間去調試一些復雜的參數,如果能寫出來也很牛皮吧。
相關文章
Adnroid 自定義ProgressDialog加載中(加載圈)
這篇文章主要介紹了Adnroid 自定義ProgressDialog加載中(加載圈),需要的朋友可以參考下2017-06-06
Android?Gradle?插件自定義Plugin實現注意事項
這篇文章主要介紹了Android?Gradle?插件自定義Plugin實現注意事項,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-06-06
Android普通應用升級為系統(tǒng)應用并獲取系統(tǒng)權限的操作
這篇文章主要介紹了Android普通應用升級為系統(tǒng)應用并獲取系統(tǒng)權限的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android8.1原生系統(tǒng)網絡感嘆號消除的方法
這篇文章主要介紹了Android8.1原生系統(tǒng)網絡感嘆號消除的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05

