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

Android如何实现带分段提示的16位借记卡号码EditText?

实现借记卡号码输入EditText的方案

核心思路

通过自定义EditText结合TextWatcher监听输入内容,同时利用SpannableString处理提示文本(Hint)的分段格式,并给横线部分设置不可选中标记,确保用户只能操作输入的数字内容。

具体实现步骤

1. 自定义EditText类

创建DebitCardEditText继承自AppCompatEditText,重写光标和选择相关方法,限制选中范围仅为用户输入的数字区域:

class DebitCardEditText @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.editTextStyle
) : AppCompatEditText(context, attrs, defStyleAttr) {

    private val separator = " "
    private val cardLength = 16
    private val segmentLength = 4
    private val totalSegments = cardLength / segmentLength

    init {
        initHint()
        inputType = InputType.TYPE_CLASS_NUMBER
        addTextChangedListener(CardTextWatcher())
    }

    private fun initHint() {
        val hintBuilder = StringBuilder()
        repeat(totalSegments) { index ->
            hintBuilder.append("----")
            if (index != totalSegments - 1) {
                hintBuilder.append(separator)
            }
        }
        // 给横线设置不可选中的Span
        val spannableHint = SpannableString(hintBuilder.toString())
        spannableHint.setSpan(
            object : Selection() {
                override fun canSelectArbitrarily(): Boolean = false
            },
            0,
            spannableHint.length,
            Spannable.SPAN_INCLUSIVE_INCLUSIVE
        )
        hint = spannableHint
    }

    // 限制选中范围仅为输入的数字区域
    override fun onSelectionChanged(selStart: Int, selEnd: Int) {
        val filteredText = text?.toString()?.replace(separator, "") ?: ""
        if (filteredText.isEmpty()) {
            setSelection(0)
            return
        }

        // 计算实际可选中的范围,跳过分隔符
        var actualStart = selStart
        var actualEnd = selEnd
        var spaceCount = 0
        for (i in 0 until selStart) {
            if (text?.get(i) == ' ') spaceCount++
        }
        actualStart -= spaceCount

        spaceCount = 0
        for (i in 0 until selEnd) {
            if (text?.get(i) == ' ') spaceCount++
        }
        actualEnd -= spaceCount

        // 确保选中范围不超出已输入内容
        val maxSelect = filteredText.length + (filteredText.length / segmentLength)
        if (actualStart > maxSelect) actualStart = maxSelect
        if (actualEnd > maxSelect) actualEnd = maxSelect

        super.onSelectionChanged(actualStart, actualEnd)
    }

    inner class CardTextWatcher : TextWatcher {
        private var isUpdating = false

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            if (isUpdating) return
            isUpdating = true

            // 过滤非数字字符
            val cleanText = s?.toString()?.replace(Regex("[^0-9]"), "") ?: ""
            val builder = StringBuilder()

            // 自动添加分隔符
            for (i in cleanText.indices) {
                if (i != 0 && i % segmentLength == 0) {
                    builder.append(separator)
                }
                builder.append(cleanText[i])
            }

            setText(builder.toString())
            setSelection(builder.length)
            isUpdating = false
        }

        override fun afterTextChanged(s: Editable?) {}
    }
}

2. 布局中引用自定义控件

在XML布局文件中直接使用该控件:

<com.yourpackage.DebitCardEditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:maxLength="19" <!-- 16位数字 + 3个空格分隔符 -->
    android:gravity="center" />

3. 关键细节说明

  • 提示文本不可选中:通过SpannableString给横线部分设置自定义SelectionSpan,重写canSelectArbitrarily返回false,阻止横线被选中。
  • 自动分段格式:TextWatcher过滤非数字内容后,每输入4位自动添加空格分隔符,保持格式规范。
  • 光标与选中控制:重写onSelectionChanged方法,计算实际可操作的数字区域,跳过分隔符和未输入的横线部分,确保用户仅能操作自己输入的数字。
  • 输入长度限制:设置maxLength为19,避免超出16位数字的输入限制。

内容的提问来源于stack exchange,提问作者james04

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 19:50:14