Android OTP输入框连续删除功能失效问题求助
OTP连续删除功能失效问题排查与修复
问题描述
我正在实现OTP账号验证功能,目前连续输入OTP数字的功能正常,但连续删除数字时功能失效,希望得到修复方案。
布局文件代码
<?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="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enter OTP" android:fontFamily="sans-serif-condensed-medium" style="@style/TextAppearance.AppCompat.Large" android:textStyle="bold" android:layout_marginTop="10dp" android:layout_gravity="center"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:layout_marginStart="30dp" android:layout_marginEnd="30dp" android:orientation="horizontal"> <EditText android:id="@+id/edit1" android:layout_width="0dp" android:layout_weight="1" android:inputType="number" android:textColorHint="@color/black" android:maxLength="1" android:imeOptions="actionPrevious" android:textStyle="bold" android:backgroundTint="@color/otpEditLineColor" style="@style/TextAppearance.AppCompat.Large" android:gravity="center" android:layout_height="match_parent"/> <EditText android:id="@+id/edit2" android:layout_width="0dp" android:layout_weight="1" android:inputType="number" android:textColorHint="@color/black" android:textStyle="bold" android:maxLength="1" android:imeOptions="actionPrevious" android:backgroundTint="@color/otpEditLineColor" style="@style/TextAppearance.AppCompat.Large" android:gravity="center" android:layout_height="match_parent"/> <EditText android:id="@+id/edit3" android:layout_width="0dp" android:layout_weight="1" android:inputType="number" android:textColorHint="@color/black" android:textStyle="bold" android:maxLength="1" android:imeOptions="actionPrevious" style="@style/TextAppearance.AppCompat.Large" android:gravity="center" android:backgroundTint="@color/otpEditLineColor" android:layout_height="match_parent"/> <EditText android:id="@+id/edit4" android:layout_width="0dp" android:layout_weight="1" android:inputType="number" android:textColorHint="@color/black" android:textStyle="bold" android:maxLength="1" android:imeOptions="actionPrevious" style="@style/TextAppearance.AppCompat.Large" android:gravity="center" android:backgroundTint="@color/otpEditLineColor" android:layout_height="match_parent"/> <EditText android:id="@+id/edit5" android:layout_width="0dp" android:layout_weight="1" android:inputType="number" android:textColorHint="@color/black" android:textStyle="bold" android:maxLength="1" android:imeOptions="actionPrevious" android:backgroundTint="@color/otpEditLineColor" style="@style/TextAppearance.AppCompat.Large" android:gravity="center" android:layout_height="match_parent"/> <EditText android:id="@+id/edit6" android:layout_width="0dp" android:layout_weight="1" android:inputType="number" android:textColorHint="@color/black" android:textStyle="bold" android:backgroundTint="@color/otpEditLineColor" style="@style/TextAppearance.AppCompat.Large" android:gravity="center" android:maxLength="1" android:imeOptions="actionPrevious" android:layout_height="match_parent"/> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:layout_marginBottom="20dp" android:layout_gravity="center" android:orientation="horizontal"> <Button android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cancel" style="@style/RectangleButtonLoginStyle" android:layout_gravity="center"/> <Button android:id="@+id/verify" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Verify" style="@style/RectangleButtonLoginStyle" android:layout_gravity="center"/> </LinearLayout> </LinearLayout>
当前实现代码
private fun fillInWithOtp(edit1: EditText, edit2: EditText, edit3: EditText, edit4: EditText, edit5: EditText, edit6: EditText) { edit1.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s!!.trim().toString().isNotEmpty()){ edit2.requestFocus() } } override fun afterTextChanged(s: Editable?) { } }) edit2.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s!!.trim().toString().isNotEmpty()){ edit3.requestFocus() } } override fun afterTextChanged(s: Editable?) { } }) edit3.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s!!.trim().toString().isNotEmpty()){ edit4.requestFocus() } } override fun afterTextChanged(s: Editable?) { } }) edit4.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s!!.trim().toString().isNotEmpty()){ edit5.requestFocus() } } override fun afterTextChanged(s: Editable?) { } }) edit5.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s!!.trim().toString().isNotEmpty()){ edit6.requestFocus() } } override fun afterTextChanged(s: Editable?) { } }) // Set up the key listener for each EditText setDeleteKeyListener(edit1, edit2) setDeleteKeyListener(edit2, edit3) setDeleteKeyListener(edit3, edit4) setDeleteKeyListener(edit4, edit5) setDeleteKeyListener(edit5, edit6) } private fun setDeleteKeyListener(currentEdit: EditText, nextEdit: EditText) { currentEdit.setOnKeyListener { _, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_DEL && event.action == KeyEvent.ACTION_DOWN) { currentEdit.text.clear() // Clear the content of current EditText if (currentEdit.isFocused && currentEdit.text.isEmpty()) { nextEdit.requestFocus() // Move focus to the next EditText } true // Return true to indicate that the event is handled } else { false // Return false if the event is not handled } } }
问题原因分析
你的删除逻辑存在两个核心问题:
- 方向搞反:删除时应该把焦点移到前一个EditText,而不是后一个。当前代码里
setDeleteKeyListener传入的是currentEdit和nextEdit,调用nextEdit.requestFocus()会导致删除后焦点往后跳,完全不符合预期。 - 时机错误:直接在
KEYCODE_DEL事件中清空文本并跳转焦点,会和系统默认的删除行为冲突,而且没有判断当前EditText是否已经为空——如果当前框本来就空,删除时应该直接跳转到前一个框,而不是重复清空。
修复后的代码
优化fillInWithOtp方法
调整删除监听的绑定逻辑,把每个EditText和前一个EditText关联:
private fun fillInWithOtp(edit1: EditText, edit2: EditText, edit3: EditText, edit4: EditText, edit5: EditText, edit6: EditText) { // 保留原有的输入跳转逻辑 edit1.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s?.trim().orEmpty().isNotEmpty()) edit2.requestFocus() } override fun afterTextChanged(s: Editable?) {} }) edit2.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s?.trim().orEmpty().isNotEmpty()) edit3.requestFocus() } override fun afterTextChanged(s: Editable?) {} }) edit3.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s?.trim().orEmpty().isNotEmpty()) edit4.requestFocus() } override fun afterTextChanged(s: Editable?) {} }) edit4.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s?.trim().orEmpty().isNotEmpty()) edit5.requestFocus() } override fun afterTextChanged(s: Editable?) {} }) edit5.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s?.trim().orEmpty().isNotEmpty()) edit6.requestFocus() } override fun afterTextChanged(s: Editable?) {} }) edit6.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun afterTextChanged(s: Editable?) {} }) // 绑定删除监听,关联前一个EditText setDeleteKeyListener(edit2, edit1) setDeleteKeyListener(edit3, edit2) setDeleteKeyListener(edit4, edit3) setDeleteKeyListener(edit5, edit4) setDeleteKeyListener(edit6, edit5) }
修复setDeleteKeyListener方法
调整逻辑:判断当前EditText是否为空,为空则跳转到前一个;不为空则先让系统完成删除,再判断是否需要跳转:
private fun setDeleteKeyListener(currentEdit: EditText, prevEdit: EditText) { currentEdit.setOnKeyListener { _, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_DEL && event.action == KeyEvent.ACTION_DOWN) { // 当前框为空时,直接跳转到前一个并选中文本 if (currentEdit.text.isNullOrEmpty()) { prevEdit.requestFocus() prevEdit.setSelection(prevEdit.text.length) return@setOnKeyListener true } // 当前框不为空时,延迟处理确保文本已删除 currentEdit.post { if (currentEdit.text.isNullOrEmpty()) { prevEdit.requestFocus() prevEdit.setSelection(prevEdit.text.length) } } } false // 非删除事件交给系统处理 } }
额外优化建议
可以把所有EditText放到一个List中,通过循环绑定监听,避免重复代码:
private fun setupOtpInputs(vararg edits: EditText) { val editList = edits.toList() editList.forEachIndexed { index, editText -> editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if(s?.trim().orEmpty().isNotEmpty() && index < editList.size - 1) { editList[index + 1].requestFocus() } } override fun afterTextChanged(s: Editable?) {} }) // 跳过第一个EditText,绑定删除监听 if (index > 0) { setDeleteKeyListener(editText, editList[index - 1]) } } }
调用时直接传setupOtpInputs(edit1, edit2, edit3, edit4, edit5, edit6)即可,代码更简洁。
内容的提问来源于stack exchange,提问作者Taki
相关产品推荐
相关产品推荐

