You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 12:44:55