Android Kotlin项目中如何实现自定义PIN码输入框?
实现单EditText的6位PIN码输入框方案
你用单个EditText加hint的方式确实达不到视觉上的多框效果,这里给你一个更优的实现方案,只用一个自定义EditText就能实现类似多输入框的视觉和交互效果:
1. 制作PIN码输入框背景
先创建背景drawable,让它看起来是6个独立的输入方框:
res/drawable/pin_background.xml
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 外层容器背景 --> <item> <shape android:shape="rectangle"> <solid android:color="#F5F5F5"/> <stroke android:width="1dp" android:color="#E0E0E0"/> <corners android:radius="8dp"/> </shape> </item> <!-- 内部6个分隔框 --> <item android:left="1dp" android:right="1dp" android:top="1dp" android:bottom="1dp"> <GridLayout android:layout_width="match_parent" android:layout_height="match_parent" android:columnCount="6" android:rowCount="1"> <View android:layout_column="0" android:layout_row="0" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/pin_single_box"/> <View android:layout_column="1" android:layout_row="0" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/pin_single_box"/> <View android:layout_column="2" android:layout_row="0" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/pin_single_box"/> <View android:layout_column="3" android:layout_row="0" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/pin_single_box"/> <View android:layout_column="4" android:layout_row="0" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/pin_single_box"/> <View android:layout_column="5" android:layout_row="0" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/pin_single_box"/> </GridLayout> </item> </layer-list>
res/drawable/pin_single_box.xml
<?xml version="1.0" encoding="utf-8"?> <shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android"> <stroke android:width="1dp" android:color="#E0E0E0"/> <solid android:color="@android:color/transparent"/> </shape>
2. 自定义PinEditText类
创建自定义EditText,通过TextWatcher自动处理输入格式化,让每个数字之间用空格分隔,同时保证光标位置正确:
import android.content.Context import android.graphics.Rect import android.text.Editable import android.text.TextWatcher import android.util.AttributeSet import androidx.appcompat.widget.AppCompatEditText class PinEditText @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle ) : AppCompatEditText(context, attrs, defStyleAttr) { private val pinLength = 6 private var isFormatting = false // 防止循环触发TextWatcher init { inputType = android.text.InputType.TYPE_CLASS_NUMBER // 强制数字键盘 maxLines = 1 gravity = android.view.Gravity.CENTER 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?) { if (isFormatting) return isFormatting = true // 过滤掉所有非数字字符 val cleanDigits = s?.toString()?.filter { it.isDigit() } ?: "" val formattedText = StringBuilder() // 给每个数字之间加空格 cleanDigits.forEachIndexed { index, c -> formattedText.append(c) if (index < cleanDigits.length - 1) { formattedText.append(" ") } } // 限制输入长度不超过6位 val finalText = if (formattedText.length > pinLength * 2 - 1) { formattedText.substring(0, pinLength * 2 - 1) } else { formattedText.toString() } setText(finalText) // 光标自动定位到文本末尾 setSelection(finalText.length) isFormatting = false } }) } // 获取纯数字的PIN码(去掉空格) fun getPin(): String { return text?.toString()?.replace(" ", "") ?: "" } // 焦点变化时确保光标位置正确 override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) { super.onFocusChanged(focused, direction, previouslyFocusedRect) if (focused) { setSelection(text?.length ?: 0) } } }
3. 在布局中使用自定义View
替换你原来的EditText,用这个自定义View:
<com.yourpackage.PinEditText android:id="@+id/pinEditText" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_marginTop="24dp" android:background="@drawable/pin_background" android:fontFamily="@font/stolzl_regular" android:letterSpacing="0.12" android:paddingVertical="10dp" android:textSize="14sp"/>
4. 额外优化建议
- 当输入满6位数字时,可以自动隐藏软键盘:
// 在PinEditText的TextWatcher中添加判断 if (cleanDigits.length == pinLength) { val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(windowToken, 0) } - 可以给当前输入位置的方框设置高亮样式,比如动态修改边框颜色,需要在自定义View中监听输入长度,然后通过绘制或者修改背景实现。
内容的提问来源于stack exchange,提问作者Artur
相关产品推荐
相关产品推荐

