android上经常会有验证码的输入要求。
网上的实现也是五花八门,有的直接使用EditText,有的使用隐藏的Edit+前台可见的多个TextView,大部分界面已经由他们定死。
我这里提供一种思路。
界面随便你的怎么写。
只需要使用我的manager类即可。
第一,将Edit,继承一个TextInputEditText类,增加额外的限制方法:
private var onSelectionChanged:((edit:FontEdit, selStart:Int, selEnd:Int)->Unit)? = null
fun doOnSelectionChanged(change:(edit:FontEdit, selStart:Int, selEnd:Int)->Unit) {
onSelectionChanged = change
}
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
super.onSelectionChanged(selStart, selEnd)
onSelectionChanged?.invoke(this, selStart, selEnd)
}
/**
* 设置最大长度
*/
fun EditText.setMaxLength(max: Int) {
addFilters(InputFilter.LengthFilter(max))
}
fun EditText?.addFilters(vararg filter: InputFilter) {
this ?: return
if (filter.isNullOrEmpty()) {
return
}
val old = this.filters
val new = arrayOf<InputFilter>(*old, *filter)
this.filters = new
}
第二步,你随意写布局,往这里传入数组形式即可。并且,代码中去判断是否是
import android.text.Editable
import android.text.TextWatcher
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import com.google.android.material.textfield.TextInputEditText
import java.lang.StringBuilder
/**
* @author allan
* Date: 2023/4/18
* Description 验证码的帮助类
* 自行随便写布局。只需要按照顺序传入列表即可。
*
*/
class CodePairListManager(private val editList:Array<TextInputEditText>) {
/**
* 用于监听跳转。
*/
var allEnterCodeListener:((String)->Unit)? = null
private val maxTagIndex:Int = editList.size - 1
private val editTextChangeList = ArrayList<TextWatcherWithHost>()
init {
val onFocusChange = View.OnFocusChangeListener { v, hasFocus ->
//logd("on focus change tag: ${v.tag} focus: $hasFocus")
val edit = v as TextInputEditText
val textLen = edit.text?.length ?: 0
if (hasFocus) {
edit.isCursorVisible = true
if (textLen > 0) {
edit.setSelection(textLen)
}
} else {
edit.isCursorVisible = false
}
}
val onKeyListener = View.OnKeyListener { v, keyCode, event ->
val edit = v as TextInputEditText
var r = false
if (event.action == KeyEvent.ACTION_DOWN) {
when (keyCode) {
KeyEvent.KEYCODE_DEL -> {
val curIndex = edit.tag as Int
setTextWithoutNotify(curIndex, "")
if (curIndex != 0) {
val prev = prevFocusEdit(curIndex)
prev?.requestFocus()
}
r = true
}
KeyEvent.KEYCODE_ENTER -> {
if (isAllHasText()) {
allEnterCodeListener?.invoke(allCombineString())
}
r = true
}
}
}
r
}
editList.forEachIndexed { index, edit ->
edit.tag = index
edit.isCursorVisible = false
edit.setMaxLength(2)
edit.inputType = EditorInfo.TYPE_CLASS_NUMBER
edit.onFocusChangeListener = onFocusChange
val watcher = TextWatcherWithHost(this, index)
editTextChangeList.add(watcher)
edit.addTextChangedListener(watcher)
edit.setOnKeyListener(onKeyListener)
edit.asOrNull<FontEdit>()?.let {
it.doOnSelectionChanged { edit, selStart, selEnd ->
//logd("sel start $selStart $selEnd")
if (selStart == 0) {
edit.setSelection(edit.text?.length ?: 0)
}
}
}
}
}
private fun nextFocusEdit(curIndex:Int) : TextInputEditText? {
return when (curIndex) {
maxTagIndex -> null
else -> editList[curIndex + 1]
}
}
private fun prevFocusEdit(curIndex:Int) : TextInputEditText? {
return when (curIndex) {
0 -> null
else -> editList[curIndex - 1]
}
}
private fun setTextWithoutNotify(editIndex:Int, text:String) {
val edit = editList[editIndex]
edit.removeTextChangedListener(editTextChangeList[editIndex])
edit.setText(text)
edit.addTextChangedListener(editTextChangeList[editIndex])
}
private fun setTextWithoutNotify(edit:TextInputEditText, text:String) {
val editIndex = edit.tag as Int
edit.removeTextChangedListener(editTextChangeList[editIndex])
edit.setText(text)
edit.addTextChangedListener(editTextChangeList[editIndex])
}
private fun isAllHasText():Boolean {
editList.forEach {
val len = it.text?.length ?: 0
if (len == 0) {
return false
}
}
return true
}
private fun allCombineString():String {
val sb = StringBuilder()
editList.forEach {
sb.append(it.text)
}
return sb.toString()
}
private class TextWatcherWithHost(private val mgr:CodePairListManager, private val index:Int) : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
//logd("index${index}: beforeTextChanged $s : $start $count $after")
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
//logd("index${index}: onTextChanged $s : $start $before $count")
if (s?.length == 2) {
mgr.setTextWithoutNotify(index, s.substring(start, start + 1))
}
}
override fun afterTextChanged(s: Editable?) {
//logd("index${index}: after text changed")
val next = mgr.nextFocusEdit(index)
next?.requestFocus()
if (mgr.isAllHasText()) {
mgr.allEnterCodeListener?.invoke(mgr.allCombineString())
}
}
}
}
几个优点:
-
- 随便自行写布局,按照数组传入;
-
- 自动跳到下一个;光标始终在末尾。