如何实现由4个独立edit text组成的4位验证码输入组件
4位分框验证码组件实现方案
核心实现不需要依赖第三方库,分布局、交互两层处理即可,完全匹配目标效果:
- 布局部分:横向排列4个等宽输入控件,每个控件限制最大输入长度为1,仅允许输入数字,控件间留固定间距;给输入控件配置状态选择器背景,聚焦、已输入状态显示高亮边框,未激活状态显示默认灰底边框即可。
- 交互部分只需要处理三类核心逻辑:
- 输入正向跳转:当前输入框填入1位有效数字后,自动将焦点移到下一个输入框;如果是最后一个输入框,输入完成后自动收起键盘,拼接4位内容触发完成回调。
- 删除反向回退:当前输入框为空时,用户触发删除操作,自动将焦点移回上一个输入框,同时清空上一个输入框的内容,适配用户快速删改的操作习惯。
- 焦点优化:任意输入框获得焦点时,自动定位光标到内容末尾,用户可以直接输入覆盖,不需要额外手动删除原有内容。
以下是Android原生的可复用实现代码,其他技术栈(Flutter、RN、Web)逻辑完全一致,替换对应API即可:
1. 自定义控件布局文件 res/layout/view_verify_code.xml
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center" android:gap="16dp"> <EditText android:id="@+id/et_code_1" android:layout_width="48dp" android:layout_height="48dp" android:inputType="number" android:maxLength="1" android:gravity="center" android:textSize="20sp" android:background="@drawable/bg_code_input"/> <EditText android:id="@+id/et_code_2" android:layout_width="48dp" android:layout_height="48dp" android:inputType="number" android:maxLength="1" android:gravity="center" android:textSize="20sp" android:background="@drawable/bg_code_input"/> <EditText android:id="@+id/et_code_3" android:layout_width="48dp" android:layout_height="48dp" android:inputType="number" android:maxLength="1" android:gravity="center" android:textSize="20sp" android:background="@drawable/bg_code_input"/> <EditText android:id="@+id/et_code_4" android:layout_width="48dp" android:layout_height="48dp" android:inputType="number" android:maxLength="1" android:gravity="center" android:textSize="20sp" android:background="@drawable/bg_code_input"/> </LinearLayout>
2. 输入框背景状态样式 res/drawable/bg_code_input.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true"> <shape android:shape="rectangle"> <solid android:color="#FFFFFF"/> <stroke android:width="2dp" android:color="#1677FF"/> <corners android:radius="8dp"/> </shape> </item> <item android:state_selected="true"> <shape android:shape="rectangle"> <solid android:color="#FFFFFF"/> <stroke android:width="2dp" android:color="#1677FF"/> <corners android:radius="8dp"/> </shape> </item> <item> <shape android:shape="rectangle"> <solid android:color="#F5F5F5"/> <stroke android:width="1dp" android:color="#D9D9D9"/> <corners android:radius="8dp"/> </shape> </item> </selector>
3. 自定义控件逻辑封装(Kotlin)
class VerifyCodeInputView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : LinearLayout(context, attrs) { private val editTexts = mutableListOf<EditText>() private var onCodeCompleteListener: ((String) -> Unit)? = null init { LayoutInflater.from(context).inflate(R.layout.view_verify_code, this, true) initEditTextList() setInputWatch() setDeleteWatch() setFocusOpt() // 默认第一个输入框获取焦点 post { editTexts[0].requestFocus() } } private fun initEditTextList() { editTexts.add(findViewById(R.id.et_code_1)) editTexts.add(findViewById(R.id.et_code_2)) editTexts.add(findViewById(R.id.et_code_3)) editTexts.add(findViewById(R.id.et_code_4)) } private fun setInputWatch() { editTexts.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) {} override fun afterTextChanged(s: Editable?) { val input = s?.toString().orEmpty() if (input.length == 1) { editText.isSelected = true editText.clearFocus() if (index < editTexts.lastIndex) { editTexts[index + 1].requestFocus() } else { // 拼接完整验证码,触发回调 val fullCode = editTexts.joinToString("") { it.text.toString().trim() } onCodeCompleteListener?.invoke(fullCode) // 自动收起键盘 val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(editText.windowToken, 0) } } else { editText.isSelected = false } } }) } } private fun setDeleteWatch() { editTexts.forEachIndexed { index, editText -> editText.setOnKeyListener { _, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_DEL && event.action == KeyEvent.ACTION_DOWN && editText.text.isEmpty() ) { if (index > 0) { val prevEt = editTexts[index - 1] prevEt.setText("") prevEt.requestFocus() return@setOnKeyListener true } } false } } } private fun setFocusOpt() { editTexts.forEach { et -> et.setOnFocusChangeListener { _, hasFocus -> if (hasFocus && et.text.isNotEmpty()) { et.setSelection(et.text.length) } } } } /** * 监听验证码输入完成 */ fun setOnCodeCompleteListener(listener: (String) -> Unit) { onCodeCompleteListener = listener } /** * 清空已输入的验证码 */ fun clear() { editTexts.forEach { it.setText("") } editTexts[0].requestFocus() } }
使用时直接在业务布局中引入该自定义控件,调用setOnCodeCompleteListener即可拿到用户输入的完整4位验证码,不需要额外处理其他逻辑。如果需要适配6位等其他长度的验证码,只需要在布局中增加对应数量的EditText,在initEditTextList方法中把新增控件加入列表即可,核心逻辑不需要改动。
效果参考:
内容的提问来源于stack exchange,提问作者Mario Muresean
相关产品推荐
相关产品推荐

