【问题标题】:Format credit card in edit text in android在android中的编辑文本中格式化信用卡
【发布时间】:2012-08-01 04:05:28
【问题描述】:

如何让EditText接受格式输入:

4digit 4digit 4digit 4digit 

我试过Custom format edit text input android to accept credit card number,但不幸的是我无法删除空格。只要有空间,我就不能删除它。请帮我找出问题。

【问题讨论】:

标签: android formatting android-edittext


【解决方案1】:

找到多个“OK”的答案后。我转向了一个更好的 TextWatcher,它旨在独立于 TextView 正常工作。

TextWatcher 类如下:

/**
 * Formats the watched EditText to a credit card number
 */
public static class FourDigitCardFormatWatcher implements TextWatcher {

    // Change this to what you want... ' ', '-' etc..
    private static final char space = ' ';

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    @Override
    public void afterTextChanged(Editable s) {
        // Remove spacing char
        if (s.length() > 0 && (s.length() % 5) == 0) {
            final char c = s.charAt(s.length() - 1);
            if (space == c) {
                s.delete(s.length() - 1, s.length());
            }
        }
        // Insert char where needed.
        if (s.length() > 0 && (s.length() % 5) == 0) {
            char c = s.charAt(s.length() - 1);
            // Only if its a digit where there should be a space we insert a space
            if (Character.isDigit(c) && TextUtils.split(s.toString(), String.valueOf(space)).length <= 3) {
                s.insert(s.length() - 1, String.valueOf(space));
            }
        }
    }
}

然后将它添加到您的 TextView 中,就像您添加任何其他 TextWatcher 一样。

{
  //...
  mEditTextCreditCard.addTextChangedListener(new FourDigitCardFormatWatcher()); 
}

这将自动删除空间,这样用户在编辑时实际上可以减少击键次数。

警告

如果您使用inputType="numberDigit",这将禁用“-”和“”字符,因此我建议使用inputType="phone"。这会启用其他字符,但只需使用自定义输入过滤器即可解决问题。

【讨论】:

  • 我还想补充一点,您可以将 inputType 保留为数字并使用 android:digit="0123456789 _"。
  • @mpellegr 这适用于空格,但如果您使用破折号作为分隔符(显示在“数字”输入键盘上),用户可以输入它。一个简单的解决方法是删除 @ 987654328@ 在第一个 if 语句中。
  • 这不支持中间字符串编辑...用户可以移动到字段中间添加/删除内容,并且间距将不正确。
  • 刚刚花了 15 分钟尝试使用 android:digit="0123456789 _" (如@mpellegr 所述),没有运气,直到我看到这里 - stackoverflow.com/questions/3402693/… 它是“数字”而不是“数字”。也许它会帮助某人。
  • @Chris.Jenkins 如果我想从用户那里输入一个 19 位的数字怎么办,它不起作用。你能帮我吗
【解决方案2】:

Example on github.com

迟到的答案,但我想它可能对某人有帮助:

    cardNumberEditText.addTextChangedListener(new TextWatcher() {

        private static final int TOTAL_SYMBOLS = 19; // size of pattern 0000-0000-0000-0000
        private static final int TOTAL_DIGITS = 16; // max numbers of digits in pattern: 0000 x 4
        private static final int DIVIDER_MODULO = 5; // means divider position is every 5th symbol beginning with 1
        private static final int DIVIDER_POSITION = DIVIDER_MODULO - 1; // means divider position is every 4th symbol beginning with 0
        private static final char DIVIDER = '-';

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // noop
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // noop
        }

        @Override
        public void afterTextChanged(Editable s) {
            if (!isInputCorrect(s, TOTAL_SYMBOLS, DIVIDER_MODULO, DIVIDER)) {
                s.replace(0, s.length(), buildCorrectString(getDigitArray(s, TOTAL_DIGITS), DIVIDER_POSITION, DIVIDER));
            }
        }

        private boolean isInputCorrect(Editable s, int totalSymbols, int dividerModulo, char divider) {
            boolean isCorrect = s.length() <= totalSymbols; // check size of entered string
            for (int i = 0; i < s.length(); i++) { // check that every element is right
                if (i > 0 && (i + 1) % dividerModulo == 0) {
                    isCorrect &= divider == s.charAt(i);
                } else {
                    isCorrect &= Character.isDigit(s.charAt(i));
                }
            }
            return isCorrect;
        }

        private String buildCorrectString(char[] digits, int dividerPosition, char divider) {
            final StringBuilder formatted = new StringBuilder();

            for (int i = 0; i < digits.length; i++) {
                if (digits[i] != 0) {
                    formatted.append(digits[i]);
                    if ((i > 0) && (i < (digits.length - 1)) && (((i + 1) % dividerPosition) == 0)) {
                        formatted.append(divider);
                    }
                }
            }

            return formatted.toString();
        }

        private char[] getDigitArray(final Editable s, final int size) {
            char[] digits = new char[size];
            int index = 0;
            for (int i = 0; i < s.length() && index < size; i++) {
                char current = s.charAt(i);
                if (Character.isDigit(current)) {
                    digits[index] = current;
                    index++;
                }
            }
            return digits;
        }
    });

这与开始字符串/结束字符串/中间字符串编辑完美配合,粘贴也完美工作。

【讨论】:

  • 不能正常工作。如果输入数字,则将光标放回字段的开头并输入“12345”,它最终会变成“1234 65”
  • @IgorTyulkanov,我将您的代码添加到我的应用程序中,我只能在 EditText 中添加 4 个数字,我忘记了什么?
  • @piotrek1543,你能把你的代码贴在 gist e. G。我会试着检查一下
  • 好的,我发现我的错误 - 糟糕 inputType :-) 解决并且工作正常,+1
  • 太棒了!谢啦!还要检查android:digits="0123456789 " 是否包含 CARD_NUMBER_DIVIDER(注意末尾的空格),否则它会因堆栈溢出而崩溃!再次感谢!
【解决方案3】:

我修改了 Chris Jenkins 的答案,使其更加健壮。这样,即使用户编辑了文本的中间,空格字符仍然会正确插入(并在错误的位置自动删除)。

要使这项工作正常进行,请确保EditText 属性设置如下(注意digits 上的空格):

android:digits="01234 56789"
android:inputType="number"
android:maxLength="19"

那么这里就是您需要的TextWatcher。匿名类也可以设为静态,因为它独立于 EditText

    yourTextView.addTextChangedListener(new TextWatcher() {
        private static final char space = ' ';

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            // Remove all spacing char
            int pos = 0;
            while (true) {
                if (pos >= s.length()) break;
                if (space == s.charAt(pos) && (((pos + 1) % 5) != 0 || pos + 1 == s.length())) {
                    s.delete(pos, pos + 1);
                } else {
                    pos++;
                }
            }

            // Insert char where needed.
            pos = 4;
            while (true) {
                if (pos >= s.length()) break;
                final char c = s.charAt(pos);
                // Only if its a digit where there should be a space we insert a space
                if ("0123456789".indexOf(c) >= 0) {
                    s.insert(pos, "" + space);
                }
                pos += 5;
            }
        }
    });

【讨论】:

  • 工作正常。但是,它不允许删除最终位置之前的空格(它只是立即将空格放回去)。
  • 同意@tom,但比其他解决方案好得多。
  • 这个解决方案非常适合我,并且比其他解决方案更简单
【解决方案4】:

这是一个使用正则表达式的更简洁的解决方案。尽管正则表达式可能效率不高,但在这种情况下它们就足够了,因为它处理最多 19 个字符的字符串,即使处理发生在每次按键之后。

editTxtCardNumber.addTextChangedListener(new TextWatcher() {

    @Override
    public void onTextChanged(CharSequence s, int arg1, int arg2,
            int arg3) { }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) { }

    @Override
    public void afterTextChanged(Editable s) {
        String initial = s.toString();
        // remove all non-digits characters
        String processed = initial.replaceAll("\\D", "");
        // insert a space after all groups of 4 digits that are followed by another digit
        processed = processed.replaceAll("(\\d{4})(?=\\d)", "$1 ");
        // to avoid stackoverflow errors, check that the processed is different from what's already
        //  there before setting
        if (!initial.equals(processed)) {
            // set the value
            s.replace(0, initial.length(), processed);
        }

    }

});

【讨论】:

  • 你在最后一个分号之前缺少一个右括号
  • 你晚上睡得怎么样!?合上括号!
  • 应标记为最佳答案。只需很少的代码即可在所有用例中完美运行。
  • 添加数字 9999,然后将光标移动到 EditText 的开头,然后添加数字 123456。结果是“1234 6599 99”而不是“1234 5699 99”。请注意,5 和 6 是相反的。
  • 正如@TheIT 所说,从开始位置到插入仍然存在错误。因为我的解决方案很简单,而且效果很好。
【解决方案5】:

这是我用于信用卡号的类。用法示例如下。

FormattedNumberEditText.kt

import android.content.Context
import android.text.Editable
import android.text.InputType
import android.text.TextWatcher
import android.text.method.DigitsKeyListener
import android.util.AttributeSet
import android.widget.EditText

open class FormattedNumberEditText : AppCompatEditText {

    var prefix = ""
        private set

    var groupSeparator = ' '
        private set

    var numberOfGroups = 4
        private set

    var groupLength = 4
        private set

    var inputLength = numberOfGroups * (groupLength + 1) - 1
        private set

    private val digitsKeyListener = DigitsKeyListener.getInstance("0123456789")

    private lateinit var separatorAndDigitsKeyListener: DigitsKeyListener

    private var initCompleted = false

    constructor(context: Context) : super(context) {
        init(null)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        init(attrs)
    }

    private fun init(attrs: AttributeSet?) {
        if (attrs != null) {
            val a = context.theme.obtainStyledAttributes(attrs, R.styleable.FormattedNumberEditText, 0, 0)
            prefix = a.getString(R.styleable.FormattedNumberEditText_prefix) ?: prefix
            val separatorStr = a.getString(R.styleable.FormattedNumberEditText_groupSeparator)
            if (!separatorStr.isNullOrEmpty()) {
                groupSeparator = separatorStr[0]
            }
            numberOfGroups = a.getInteger(R.styleable.FormattedNumberEditText_numberOfGroups, numberOfGroups)
            groupLength = a.getInteger(R.styleable.FormattedNumberEditText_groupLength, groupLength)
        }

        inputLength = numberOfGroups * (groupLength + 1) - 1
        separatorAndDigitsKeyListener = DigitsKeyListener.getInstance("0123456789$groupSeparator")

        setText(prefix)
        setSelection(text!!.length)
        inputType = InputType.TYPE_CLASS_NUMBER
        keyListener = digitsKeyListener
        addTextChangedListener(TextChangeListener())

        initCompleted = true
    }

    override fun onSelectionChanged(start: Int, end: Int) {
        if (!initCompleted) {
            return
        }

        // make sure input always starts with the prefix
        if (!text!!.startsWith(prefix)) {
            setText(prefix)
            setSelection(text!!.length, text!!.length)
            return
        }

        // make sure cursor is always at the end of the string
        if (start != text!!.length || end != text!!.length) {
            setSelection(text!!.length)
        } else {
            super.onSelectionChanged(start, end)
        }
    }

    private inner class TextChangeListener : TextWatcher {

        var textBefore = ""
        var enteredText = ""
        var deletedChars = 0

        var listenerEnabled = true

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            if (!listenerEnabled) return

            textBefore = text.toString()
            enteredText = ""
            deletedChars = 0
        }

        override fun onTextChanged(text: CharSequence?, start: Int, lengthBefore: Int, lengthAfter: Int) {
            if (!listenerEnabled) return

            if (text == null) {
                deletedChars = textBefore.length
                return
            }

            if (text.length < textBefore.length) {
                deletedChars = textBefore.length - text.length
                return
            }

            enteredText = text.toString().substring(textBefore.length, text.length)
        }

        override fun afterTextChanged(s: Editable?) {
            if (!listenerEnabled) return

            if (s == null) {
                return
            }

            listenerEnabled = false

            if (deletedChars > 0) {
                handleTextChange(s)
            } else {
                if (enteredText.length > 1) {
                    s.replace(s.length - enteredText.length, s.length, "")

                    // Append one char at a time
                    enteredText.forEach {
                        s.append("$it")
                        handleTextChange(s)
                    }
                } else {
                    handleTextChange(s)
                }
            }

            listenerEnabled = true
        }

        fun handleTextChange(s: Editable) {
            if (s.length > inputLength) {
                while (s.length > inputLength) {
                    s.delete(s.length - 1, s.length)
                }
            } else if (s.isNotEmpty() && s.length % (groupLength + 1) == 0) {
                if (s.last() == groupSeparator) {
                    s.delete(s.length - 1, s.length)
                } else if (s.last().isDigit() && s.length < inputLength) {
                    keyListener = separatorAndDigitsKeyListener
                    s.insert(s.length - 1, groupSeparator.toString())
                    keyListener = digitsKeyListener
                }
            }
        }

    }

}

attrs.xml(属于/res/values)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="FormattedNumberEditText">
        <attr name="prefix" format="string" />
        <attr name="numberOfGroups" format="integer" />
        <attr name="groupLength" format="integer" />
        <attr name="groupSeparator" format="string" />
    </declare-styleable>
</resources>

使用示例

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Credit card number" />

    <com.example.myapplication.FormattedNumberEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Credit card number (different separator)" />

    <com.example.myapplication.FormattedNumberEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:groupSeparator="-" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Phone number starting with +370" />

    <com.example.myapplication.FormattedNumberEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:groupLength="13"
        app:groupSeparator=" "
        app:numberOfGroups="1"
        app:prefix="+370\u0020" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="IBAN number starting with LT" />

    <com.example.myapplication.FormattedNumberEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:groupLength="4"
        app:groupSeparator=" "
        app:numberOfGroups="5"
        app:prefix="LT" />

</LinearLayout>

【讨论】:

  • 最佳答案,Kotlin 也是如此。谢谢@Egis
  • 用于删除后面的空格 val ccNumber = cardNumber.replace("\\s".toRegex(), "")
【解决方案6】:

我正在将我的解决方案添加到列表中。据我所知,它没有缺点。您可以在中间进行编辑,删除空格字符,复制粘贴等。

为了允许在字符串中的任何位置进行编辑并保持光标位置,遍历 Editable 并一一取出所有空格(如果有)。然后在适当的位置添加新的空格。这将确保光标随着对内容所做的更改而移动。

import java.util.LinkedList;


import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;


/**
 * Formats the watched EditText to groups of characters, with spaces between them.
 */
public class GroupedInputFormatWatcher implements TextWatcher {

    private static final char SPACE_CHAR = ' ';
    private static final String SPACE_STRING = String.valueOf(SPACE_CHAR);
    private static final int GROUPSIZE = 4;

    /**
     * Breakdown of this regexp:
     * ^             - Start of the string
     * (\\d{4}\\s)*  - A group of four digits, followed by a whitespace, e.g. "1234 ". Zero or more times.
     * \\d{0,4}      - Up to four (optional) digits.
     * (?<!\\s)$     - End of the string, but NOT with a whitespace just before it.
     * 
     * Example of matching strings:
     *  - "2304 52"
     *  - "2304"
     *  - ""
     */
    private final String regexp = "^(\\d{4}\\s)*\\d{0,4}(?<!\\s)$";
    private boolean isUpdating = false;

    private final EditText editText;

    public GroupedInputFormatWatcher(EditText editText) {
        this.editText = editText;
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        String originalString = s.toString();

        // Check if we are already updating, to avoid infinite loop.
        // Also check if the string is already in a valid format.
        if (isUpdating || originalString.matches(regexp)) {
            return;
        }

        // Set flag to indicate that we are updating the Editable.
        isUpdating = true;

        // First all whitespaces must be removed. Find the index of all whitespace.
        LinkedList<Integer> spaceIndices = new LinkedList <Integer>();
        for (int index = originalString.indexOf(SPACE_CHAR); index >= 0; index = originalString.indexOf(SPACE_CHAR, index + 1)) {
            spaceIndices.offerLast(index);
        }

        // Delete the whitespace, starting from the end of the string and working towards the beginning.
        Integer spaceIndex = null;
        while (!spaceIndices.isEmpty()) {
            spaceIndex = spaceIndices.removeLast();
            s.delete(spaceIndex, spaceIndex + 1);
        }

        // Loop through the string again and add whitespaces in the correct positions
        for(int i = 0; ((i + 1) * GROUPSIZE + i) < s.length(); i++) {
            s.insert((i + 1) * GROUPSIZE + i, SPACE_STRING);
        }

        // Finally check that the cursor is not placed before a whitespace.
        // This will happen if, for example, the user deleted the digit '5' in
        // the string: "1234 567".
        // If it is, move it back one step; otherwise it will be impossible to delete
        // further numbers.
        int cursorPos = editText.getSelectionStart();
        if (cursorPos > 0 && s.charAt(cursorPos - 1) == SPACE_CHAR) {
            editText.setSelection(cursorPos - 1);
        }

        isUpdating = false;
    }
}

【讨论】:

  • 这种方法不允许自动删除非前导空格。添加文本 123456,将其转换为“1234 56”,现在将光标移动到 5 之前,即“1234 |56”,现在尝试删除空格。我希望数字 4 被删除,但光标却被移动了。
  • 话虽如此,这是迄今为止我找到的最佳答案
【解决方案7】:

不确定 TextWatcher 是否适合使用 - 我们应该使用 InputFilter

根据 Android 文档,TextWatcher 应该用于外部使用示例: 一个 [EditView] 用于密码输入+ 一个显示“弱”、“强”等的 [TextView] 视图...

对于信用卡格式,我使用的是InputFilter

public class CreditCardInputFilter implements InputFilter {
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        if (dest != null & dest.toString().trim().length() > 24) return null;
        if (source.length() == 1 && (dstart == 4 || dstart == 9 || dstart == 14))
            return " " + new String(source.toString());
        return null; // keep original
    }
}

并结合长度过滤器(Android SDK):

mEditCardNumber.setFilters(new InputFilter[]{
     new InputFilter.LengthFilter(24),
     new CreditCardInputFilter(),
});

这处理输入和删除数字时的情况。

(!) 但这不能处理复制/粘贴整个字符串的情况, 这应该在不同的 InputFilter 类中完成

希望对你有帮助!

【讨论】:

  • 关心良好实践和高效工作的人!
  • 我有你所拥有的,但正如你所提到的,我正在努力复制/粘贴整个字符串。由于我不得不增加额外空格的长度,现在用户可以复制/粘贴该长度的字符串,然后我该怎么办?将附加字符(分隔符)添加到值的整个想法对我来说似乎很可悲,因为在提取值进行验证时,您也必须删除这些分隔符。我希望在 Android 中有一种更简洁的方法来做到这一点。无论如何,你能指出我如何解决复制/粘贴问题的正确方向吗?是通过剪贴板管理器吗?
  • 不适用于包含字符的文本(例如 IBAN)
  • 我稍微改进了这个输入过滤器,支持复制/粘贴。 gist.github.com/Anrimian/168f3ed6b12df7d7da6230f418171dbd
  • 这非常适合我想要做的事情。非常感谢!
【解决方案8】:

我刚刚完成了下一个实现,对我来说效果很好,即使在 EditText 的任何位置粘贴和键入新文本。

Gist file

/**
 * Text watcher for giving "#### #### #### ####" format to edit text.
 * Created by epool on 3/14/16.
 */
public class CreditCardFormattingTextWatcher implements TextWatcher {

    private static final String EMPTY_STRING = "";
    private static final String WHITE_SPACE = " ";
    private String lastSource = EMPTY_STRING;

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }

    @Override
    public void afterTextChanged(Editable s) {
        String source = s.toString();
        if (!lastSource.equals(source)) {
            source = source.replace(WHITE_SPACE, EMPTY_STRING);
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < source.length(); i++) {
                if (i > 0 && i % 4 == 0) {
                    stringBuilder.append(WHITE_SPACE);
                }
                stringBuilder.append(source.charAt(i));
            }
            lastSource = stringBuilder.toString();
            s.replace(0, s.length(), lastSource);
        }
    }

}

用法: editText.addTextChangedListener(new CreditCardFormattingTextWatcher());

【讨论】:

  • 嗨@epool!该代码运行良好(甚至删除了整个文本中间的一个字符)。谢谢你!但是,当我尝试使用不同的格式进行屏蔽时(出于其他目的,例如在位置 14 处插入字符“-”),光标在删除一个字符后,在其大小为 16 的字符串中返回两个位置。我的editText的maxLength是16,你知道怎么解决吗?
  • 我不确定,我需要看看你的代码或者你到底想要做什么,也许你可以分享你的代码的 gits 链接。
  • 我现在注意到,如果您的 EditText 的当前文本是 1234 5(使用您的方法)并且您需要删除字符“3”,例如,EditText 的光标会后退两个位置。如果您需要查看我的问题,我可以创建一个 Gist,但您可以看到这个问题发生在您自己的课程中。
  • 这个问题你是对的,也许你可以用int selectionStart = yourEditText.getSelectionStart(); int selectionEnd = yourEditText.getSelectionEnd(); yourEditText.setSelection(selectionStart, selectionEnd);来解决它。
  • 我在这里试过,但它不起作用。如果你能解决它,我(可能还有其他人)会感激你。无论如何感谢@epool!
【解决方案9】:

此实现可确保正确放置间距字符,即使用户编辑中间字符串也是如此。还支持出现在软键盘上的其他字符(如破折号);也就是说,用户不能输入它们。可以进行的一项改进:此实现不允许删除字符串中间的空格字符。

public class CreditCardTextWatcher implements TextWatcher {

    public static final char SPACING_CHAR = '-'; // Using a Unicode character seems to stuff the logic up.

    @Override
    public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { }

    @Override
    public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { }

    @Override
    public void afterTextChanged(final Editable s) {
        if (s.length() > 0) {

            // Any changes we make to s in here will cause this method to be run again.  Thus we only make changes where they need to be made,
            // otherwise we'll be in an infinite loop.

            // Delete any spacing characters that are out of place.
            for (int i=s.length()-1; i>=0; --i) {
                if (s.charAt(i) == SPACING_CHAR  // There is a spacing char at this position ,
                        && (i+1 == s.length()    // And it's either the last digit in the string (bad),
                        || (i+1) % 5 != 0)) {    // Or the position is not meant to contain a spacing char?

                    s.delete(i,i+1);
                }
            }

            // Insert any spacing characters that are missing.
            for (int i=14; i>=4; i-=5) {
                if (i < s.length() && s.charAt(i) != SPACING_CHAR) {
                    s.insert(i, String.valueOf(SPACING_CHAR));
                }
            }
        }
    }
}

与适当的 PasswordTransformationMethod 实现配合使用以屏蔽 CC 数字。

【讨论】:

    【解决方案10】:

    如果您使用的是 Kotlin,这可能会有所帮助:

    class CreditCardTextFormatter(
        private var separator: String = " - ",
        private var divider: Int = 5
    ) : TextWatcher {
    
        override fun afterTextChanged(s: Editable?) {
            if (s == null) {
                return
            }
            val oldString = s.toString()
            val newString = getNewString(oldString)
            if (newString != oldString) {
                s.replace(0, oldString.length, getNewString(oldString))
            }
        }
    
        private fun getNewString(value: String): String {
    
            var newString = value.replace(separator, "")
    
            var divider = this.divider
            while (newString.length >= divider) {
                newString = newString.substring(0, divider - 1) + this.separator + newString.substring(divider - 1)
                divider += this.divider + separator.length - 1
            }
            return newString
        }
    
        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }
    
        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }
    }
    

    XML:

    <EditText
            android:id="@+id/etCardNumber"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:digits="0123456789- "
            android:inputType="number"
            android:hint="____ - ____ - ____ - ____"
            android:maxLength="25" />
    

    以及如何使用它:

    etCardNumber.addTextChangedListener(CreditCardTextFormatter())
    

    【讨论】:

    • 完美实现我的需求。
    【解决方案11】:

    我认为无论是中间文本操作还是复制粘贴操作,我的解决方案都能很好地工作。

    请看下面的代码,

      class BankNumberTextWatcher implements TextWatcher {
          private int previousCodeLen = 0;
    
          @Override
          public void beforeTextChanged(CharSequence s, int start, int count, int after) {
          }
    
          @Override
          public void onTextChanged(CharSequence s, int start, int before, int count) {
          }
    
          @Override
          public void afterTextChanged(Editable s) {
              if (s.length() > 0) {
                  String numbersOnly = s.toString().replaceAll("[^0-9]", "");
                  // current code pattern miss-match, then handle cursor position and format the code
                  handleEditInput(numbersOnly);
              } else {
                  previousCodeLen = 0;
              }
          }
    
          /**
           * Handle EditText input process for credit card including insert, delete during middle position,
           * end position or copy-paste controller
           *
           * @param numbersOnly the pure number without non-digital characters
           */
          private void handleEditInput(final String numbersOnly) {
              String code = formatNumbersAsCode(numbersOnly);
              int cursorStart = etBankCardNumber.getSelectionStart();
              etBankCardNumber.removeTextChangedListener(this);
              etBankCardNumber.setText(code);
              int codeLen = code.length();
              if (cursorStart != codeLen) {
                 // middle-string operation
                 if (cursorStart > 0 && cursorStart % 5 == 0) {
                    if (codeLen > previousCodeLen) {
                        // insert, move cursor to next
                        cursorStart++;
                    } else if (codeLen < previousCodeLen) {
                        // delete, move cursor to previous
                        cursorStart--;
                    }
                 }
                 etBankCardNumber.setSelection(cursorStart);
              } else {
                 // end-string operation
                 etBankCardNumber.setSelection(codeLen);
              }
              etBankCardNumber.addTextChangedListener(this);
              previousCodeLen = codeLen;
          }
    
          /**
           * formats credit code like 1234 1234 5123 1234
           *
           * @param s
           * @return
           */
           public String formatNumbersAsCode(CharSequence s) {
              if (TextUtils.isEmpty(s)) {
                return "";
              }
              int len = s.length();
              StringBuilder tmp = new StringBuilder();
              for (int i = 0; i < len; ++i) {
                  tmp.append(s.charAt(i));
                  if ((i + 1) % 4 == 0 && (i + 1) != len) {
                      tmp.append(" ");
                  }
              }
              return tmp.toString();
            }
      }
    

    将 inputType 设置为 EditText 的编号以避免布局文件中的其他字符。

    希望对你有所帮助。

    【讨论】:

      【解决方案12】:

      请看这个project。 Android 表单编辑文本是 EditText 的扩展,为编辑文本带来了数据验证功能

      【讨论】:

      • 希望我早点知道,但我想知道为什么我现有的代码无法删除空格。
      • 该项目看起来很有趣,但它似乎基本上是一个验证器,而不是一个输入格式化程序,这是 OP(和我)需要的。从任何屏幕截图中我都看不到它也可以输入格式。是吗?
      【解决方案13】:

      经过大量搜索并没有得到任何满意的答案来满足我的需求,我最终编写了自己的函数。

      以下是根据输入的卡类型格式化输入信用卡详细信息的示例。目前它处理 Visa、MasterCard 和 American Express 以进行格式化。

          editTxtCardNumber.addTextChangedListener(new TextWatcher() {
      
              private boolean spaceDeleted;
      
              @Override
              public void onTextChanged(CharSequence s, int arg1, int arg2,
                      int arg3) {
      
              }
      
              @Override
              public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                  CharSequence charDeleted = s.subSequence(start, start + count);
                  spaceDeleted = " ".equals(charDeleted.toString());
              }
      
              @Override
              public void afterTextChanged(Editable editable) {
      
                  if(editTxtCardNumber.getText().length() > 0 && editTxtCardNumber.getText().charAt(0) == '3') {
                      editTxtCardNumber.setFilters(new InputFilter[] { new InputFilter.LengthFilter(Constants.MAX_LENGTH_CARD_NUMBER_AMEX) });
      
                      editTxtCardNumber.removeTextChangedListener(this);
                      int cursorPosition = editTxtCardNumber.getSelectionStart();
                      String withSpaces = formatTextAmEx(editable);
                      editTxtCardNumber.setText(withSpaces);
                      editTxtCardNumber.setSelection(cursorPosition + (withSpaces.length() - editable.length()));
      
                      if (spaceDeleted) {
                          editTxtCardNumber.setSelection(editTxtCardNumber.getSelectionStart() - 1);
                          spaceDeleted = false;
                      }
      
                      editTxtCardNumber.addTextChangedListener(this);
                  } else if(editTxtCardNumber.getText().length() > 0 
                          && (editTxtCardNumber.getText().charAt(0) == '4' || editTxtCardNumber.getText().charAt(0) == '5')) {
                      editTxtCardNumber.setFilters(new InputFilter[] { new InputFilter.LengthFilter(Constants.MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD) });
      
                      editTxtCardNumber.removeTextChangedListener(this);
                      int cursorPosition = editTxtCardNumber.getSelectionStart();
                      String withSpaces = formatTextVisaMasterCard(editable);
                      editTxtCardNumber.setText(withSpaces);
                      editTxtCardNumber.setSelection(cursorPosition + (withSpaces.length() - editable.length()));
      
                      if (spaceDeleted) {
                          editTxtCardNumber.setSelection(editTxtCardNumber.getSelectionStart() - 1);
                          spaceDeleted = false;
                      }
      
                      editTxtCardNumber.addTextChangedListener(this);
                  } else {
                      editTxtCardNumber.setFilters(new InputFilter[] { new InputFilter.LengthFilter(Constants.MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD) });
      
                      editTxtCardNumber.removeTextChangedListener(this);
                      int cursorPosition = editTxtCardNumber.getSelectionStart();
                      String withSpaces = formatTextVisaMasterCard(editable);
                      editTxtCardNumber.setText(withSpaces);
                      editTxtCardNumber.setSelection(cursorPosition + (withSpaces.length() - editable.length()));
      
                      if (spaceDeleted) {
                          editTxtCardNumber.setSelection(editTxtCardNumber.getSelectionStart() - 1);
                          spaceDeleted = false;
                      }
      
                      editTxtCardNumber.addTextChangedListener(this);
                  }
              }
          });
      
          private String formatTextVisaMasterCard(CharSequence text)
          {
              StringBuilder formatted = new StringBuilder();
              int count = 0;
              for (int i = 0; i < text.length(); ++i)
              {
                  if (Character.isDigit(text.charAt(i)))
                  {
                      if (count % 4 == 0 && count > 0)
                          formatted.append(" ");
                      formatted.append(text.charAt(i));
                      ++count;
                  }
              }
              return formatted.toString();
          }
      
          private String formatTextAmEx(CharSequence text)
          {
              StringBuilder formatted = new StringBuilder();
              int count = 0;
              for (int i = 0; i < text.length(); ++i)
              {
                  if (Character.isDigit(text.charAt(i)))
                  {
                      if (count > 0 && ((count == 4) || (count == 10))) {
                          formatted.append(" ");
                      }
                      formatted.append(text.charAt(i));
                      ++count;
                  }
              }
              return formatted.toString();
          }
      

      除了格式化空格外,我还应用了检查以确保卡号不超过其最大限制,并且当达到最大限制时,通过更改字体来通知用户他已输入所有数字。这是执行上述操作的函数。

      public void checkCardNoEnteredCorrectly() {
      if(editTxtCardNumber.getText().length() > 0 && editTxtCardNumber.getText().charAt(0) == '3') {
          if(editTxtCardNumber.getText().length() == Constants.MAX_LENGTH_CARD_NUMBER_AMEX) {
              editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.amex), null, null, null);
          } else {
              editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.amex), null, null, null);
          }
      } else if(editTxtCardNumber.getText().length() > 0 && editTxtCardNumber.getText().charAt(0) == '4') {
          if(editTxtCardNumber.getText().length() == Constants.MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD) {
              editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.visa), null, null, null);
          } else {
              editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.visa), null, null, null);
          }
      } else if(editTxtCardNumber.getText().length() > 0 && editTxtCardNumber.getText().charAt(0) == '5') {
          if(editTxtCardNumber.getText().length() == Constants.MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD) {
              editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.master_card), null, null, null);
          } else {
              editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.master_card), null, null, null);
          }
      } else {
          editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.credit_card_number), null, null, null);
      }
      

      }

      注意:Constants.java中的声明如下:

      public static final int MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD = 19;
      public static final int MAX_LENGTH_CARD_NUMBER_AMEX = 17;
      

      希望对你有帮助!

      【讨论】:

        【解决方案14】:

        您可能已经想通了,但这就是我所做的。我必须重写的唯一方法是 AfterTextChanged。

        检查信用卡的形式是否已经有效,防止无限递归的基本情况

        如果表单无效,请删除所有空格,然后复制到另一个字符串中,并在适当的地方插入空格。

        然后简单地用你的新字符串替换可编辑的。

        如果您需要特定步骤的代码,请随时询问。

        Preethi,你不能删除空格的原因是你不能在 onTextChanged 回调中改变文本。来自开发者网站:

        public abstract void onTextChanged (CharSequence s, int start, int before, int count) 在 API 级别 1 中添加

        调用此方法是为了通知您,在 s 内,从 start 开始的 count 个字符刚刚替换了之前有 length 的旧文本。尝试通过此回调对 s 进行更改是错误的。

        【讨论】:

          【解决方案15】:
          int          keyDel;
          String       a;
          String       a0;
          int          isAppent = 0;
          final String ch       = " ";
          
          private void initListner() {
          
          
              txtCreditNumber.addTextChangedListener(new TextWatcher() {
          
                  @Override
                  public void onTextChanged(CharSequence s, int start, int before, int count) {
          
                      boolean flag = true;
                      if (s.length() > 19) {
                          txtCreditNumber.setText(a0);
                          txtCreditNumber.setSelection(txtCreditNumber.getText().length());
                          return;
                      }
                      String eachBlock[] = s.toString().split(ch);
                      for(int i = 0; i < eachBlock.length; i++) {
                          if (eachBlock[i].length() > 4) {
                              flag = false;
                          }
                      }
                      if (a0.length() > s.toString().length()) {
                          keyDel = 1;
                      }
                      if (flag) {
                          if (keyDel == 0) {
          
                              if (((txtCreditNumber.getText().length() + 1) % 5) == 0) {
          
                                  if (s.toString().split(ch).length <= 3) {
                                      isAppent = 1;
                                      txtCreditNumber.setText(s + ch);
                                      isAppent = 0;
                                      txtCreditNumber.setSelection(txtCreditNumber.getText().length());
                                      a = txtCreditNumber.getText().toString();
                                      return;
                                  }
                              }
                              if (isAppent == 0) {
                                  String str = s.toString();
                                  if (str.lastIndexOf(ch) == str.length() - 1) {
                                      str = str.substring(0, str.lastIndexOf(ch));
                                      keyDel = 1;
                                      txtCreditNumber.setText(str);
                                      keyDel = 0;
                                      txtCreditNumber.setSelection(txtCreditNumber.getText().length());
                                      a = txtCreditNumber.getText().toString();
                                      return;
                                  }
                              }
          
                          }
                          else {
                              String str = s.toString();
                              if (str.length() > 0 && str.lastIndexOf(ch) == str.length() - 1) {
                                  str = str.substring(0, str.lastIndexOf(ch));
                                  keyDel = 1;
                                  txtCreditNumber.setText(str);
                                  keyDel = 0;
                                  txtCreditNumber.setSelection(txtCreditNumber.getText().length());
                                  a = txtCreditNumber.getText().toString();
                                  return;
                              }
                              else {
                                  a = txtCreditNumber.getText().toString();
                                  keyDel = 0;
                              }
                          }
          
                      }
                      else {
                          String str = s.toString();
                          str = str.substring(0, str.length() - 1) + ch + str.substring(str.length() - 1, str.length());
          
                          a = str;
                          txtCreditNumber.setText(a);
                          txtCreditNumber.setSelection(txtCreditNumber.getText().length());
                      }
          
                  }
          
                  @Override
                  public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                      // TODO Auto-generated method stub
                      a0 = s.toString();
                  }
          
                  @Override
                  public void afterTextChanged(Editable s) {
                  }
              });
          }
          

          【讨论】:

            【解决方案16】:

            这是一个适当地使用所有功能来做出决定的示例。 代码可能会更长一些,但它会更快,因为它主要使用给定值的函数(开始,之前,计数......)。 此示例在用户使用退格时每 4 位添加“-”,并删除它们。 同样,确保光标位于末尾。

            public class TextWatcherImplement implements TextWatcher {
            
            private EditText creditCard;
            private String beforeText, currentText;
            private boolean noAction, addStroke, dontAddChar, deleteStroke;
            
            public TextWatcherImplement(EditText creditCard) {
                // TODO Auto-generated constructor stub
                this.creditCard = creditCard;
                noAction = false;
                addStroke = false;
                dontAddChar = false;
                deleteStroke = false;
            }
            
            /* here I save the previous string if the max character had achieved */
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // TODO Auto-generated method stub
                Log.i("TextWatcherImplement", "beforeTextChanged start==" + String.valueOf(start) + " count==" + String.valueOf(count) + " after==" + String.valueOf(after));
                if (start >= 19)
                    beforeText = s.toString();
            }
            
            
            /* here I check were we add a character, or delete one. 
            if we add character and it is time to add a stroke, then I flag it -> addStroke 
            if we delete a character and it time to delete a stroke, I flag it -> deleteStroke
            if we are in max character for the credit card, don't add char -> dontAddChar 
            */
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // TODO Auto-generated method stub
                Log.i("TextWatcherImplement", "onTextChanged start==" + String.valueOf(start) + " before==" + String.valueOf(before) + " count==" + String.valueOf(count) + " noAction ==" + String.valueOf(noAction));
                if ( (before < count) && !noAction ) {
                    if ( (start == 3) || (start == 8) || (start == 13) ) {
                        currentText = s.toString();
                        addStroke = true;
                    } else if (start >= 19) {
                        currentText = s.toString();
                        dontAddChar = true;
                    }
                } else {
                    if ( (start == 4) ||  (start == 9) ||  (start == 14)  ) { //(start == 5) || (start == 10) || (start == 15)
                        currentText = s.toString();
                        deleteStroke = true;
                    }
                }
            }
            
            /* noAction flag is when we change the text, the interface is being called again.
            the NoAction flag will prevent any action, and prevent a ongoing loop */
            
            @Override
            public void afterTextChanged(Editable stext) {
                // TODO Auto-generated method stub
                if (addStroke) {
                    Log.i("TextWatcherImplement", "afterTextChanged String == " + stext + " beforeText == " + beforeText + " currentText == " + currentText);
                    noAction = true;
                    addStroke = false;
                    creditCard.setText(currentText + "-");
                } else if (dontAddChar) {
                    dontAddChar = false;
                    noAction = true;
                    creditCard.setText(beforeText);
                } else if (deleteStroke) {
                    deleteStroke = false;
                    noAction = true;
                    currentText = currentText.substring(0, currentText.length() - 1);
                    creditCard.setText(currentText);
                } else {
                    noAction = false;
                    creditCard.setSelection(creditCard.getText().length()); // set cursor at the end of the line.
                }
            }
            

            }

            【讨论】:

              【解决方案17】:

              这是我的解决方案。我的 cmets 应该足以让 Android 开发人员了解正在发生的事情,但如果您有任何问题,请随时提问,我会尽我所能回答。

              private KeyEvent keyEvent;
              
              final TextWatcher cardNumberWatcher = new TextWatcher() {
                      @Override
                      public void beforeTextChanged(CharSequence charSequence, int start, int before, int count) {
                          // NOT USING
                      }
              
                      @Override
                      public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
                          // NOT USING
                      }
              
                      @Override
                      public void afterTextChanged(Editable editable) {
                          String cardNumbersOnly = editable.toString().replace("-", "");
              
                          /**
                          * @PARAM keyEvent
                          * This gets called upon deleting a character so you must keep a 
                          * flag to ensures this gets skipped during character deletion
                          */
                          if (cardNumbersOnly.length() >= 4 && keyEvent == null) {
                              formatCreditCardTextAndImage(this);
                          }
              
                          keyEvent = null;
                      }
                  };
              
                  cardNumberEditText.addTextChangedListener(cardNumberWatcher);
              
                  /**
                  * @LISTENER
                  * Must keep track of when the backspace event has been fired to ensure    
                  * that the delimiter character and the character before it is deleted 
                  * consecutively to avoid the user from having to press backspace twice 
                  */
                  cardNumberEditText.setOnKeyListener(new View.OnKeyListener() {
                      @Override
                      public boolean onKey(View v, int keyCode, KeyEvent event) {
                          if (event.getAction() != KeyEvent.ACTION_UP) {
                              // Hold reference of key event for checking within the text watcher
                              keyEvent = event;
                              String cardNumberString = cardNumberEditText.getText().toString();
              
                              if (keyCode == event.KEYCODE_DEL) {
                                  if (cardNumberString.substring(cardNumberString.length() - 1).equals("-")) {
                                      // Remove listener to avoid infinite looping
                                      cardNumberEditText.removeTextChangedListener(cardNumberWatcher);
                                      // Remove hyphen and character before it
                                      cardNumberEditText.setText(cardNumberString.substring(0, cardNumberString.length() - 1));
                                      // Set the cursor back to the end of the text
                                      cardNumberEditText.setSelection(cardNumberEditText.getText().length());
                                      // Add the listener back
                                      cardNumberEditText.addTextChangedListener(cardNumberWatcher);
                                  }
                                  else if (cardNumberString.length() < 2) {
                                      cardNumberBrandImageView.setImageDrawable(null);
                                      cardNumberBrandImageView.setVisibility(View.INVISIBLE);
                                  }
                              }
                          }
                          return false;
                      }
                  });
              }
              
              private void formatCreditCardTextAndImage (TextWatcher textWatcher) {
                  // Remove to avoid infinite looping
                  cardNumberEditText.removeTextChangedListener(textWatcher);
              
                  String cardNumberString = cardNumberEditText.getText().toString();
              
                  /**
                  * @CONDITION
                  * Append delimiter after every fourth character excluding the 16th
                  */
                  if ((cardNumberString.length() + 1) % 5 == 0 && !cardNumberString.substring(cardNumberString.length() - 1).equals("-")) {
                          cardNumberEditText.setText(cardNumberString + "-");
                  }
              
                  // Set the cursor back to the end of the text
                  cardNumberEditText.setSelection(cardNumberEditText.getText().length());
                  cardNumberEditText.addTextChangedListener(textWatcher);
              
                  /**
                  * @CardBrand
                  * Is an enum utility class that checks the card numbers 
                  * against regular expressions to determine the brand and updates the UI
                  */
                  if (cardNumberString.length() == 2) {
                      switch (CardBrand.detect(cardNumberEditText.getText().toString())) {
                          case VISA:
                              cardNumberBrandImageView.setImageResource(R.drawable.visa);
                              cardNumberBrandImageView.setVisibility(View.VISIBLE);
                              card.setBrand(Brand.Visa);
                              break;
                          case MASTERCARD:
                              cardNumberBrandImageView.setImageResource(R.drawable.mastercard);
                              cardNumberBrandImageView.setVisibility(View.VISIBLE);
                              card.setBrand(Brand.MasterCard);
                              break;
                          case DISCOVER:
                              cardNumberBrandImageView.setImageResource(R.drawable.discover);
                              cardNumberBrandImageView.setVisibility(View.VISIBLE);
                              card.setBrand(Brand.Discover);
                              break;
                          case AMERICAN_EXPRESS:
                              cardNumberBrandImageView.setImageResource(R.drawable.americanexpress);
                              cardNumberBrandImageView.setVisibility(View.VISIBLE);
                              card.setBrand(Brand.AmericanExpress);
                              break;
                          case UNKNOWN:
                              cardNumberBrandImageView.setImageDrawable(null);
                              cardNumberBrandImageView.setVisibility(View.INVISIBLE);
                              card.setBrand(null);
                              break;
                      }
                  }
              }
              

              【讨论】:

                【解决方案18】:

                这是一个使用TextWatcher 类的简单且易于定制的解决方案。可以使用addTextChangedListener() 方法将其分配给您的EditText

                new TextWatcher() {
                    /** Formats the Field to display user-friendly separation of the input values. */
                    @Override public final void afterTextChanged(final Editable pEditable) {
                        // Declare the separator.
                        final char lSeparator      = '-';
                        // Declare the length of separated text. i.e. (XXXX-XXXX-XXXX)
                        final int  lSeparationSize = 4;
                        // Declare the count; tracks the number of allowed characters in a row.
                              int lCount          = 0;
                        // Iterate the Characters.
                        for(int i = 0; i < pEditable.length(); i++) {
                            // Fetch the current character.
                            final char c              = pEditable.charAt(i);
                            // Is it a usual character. Here, we permit alphanumerics only.
                            final boolean lIsExpected = (Character.isDigit(c) || Character.isLetter(c)) && (c != lSeparator);
                            // Is the character expected?
                            if(lIsExpected) {
                                // Increase the count.
                                lCount++;
                            }
                            else {
                                // Is it a separator?
                                if(c == lSeparator) {
                                    // Reset the count.
                                    lCount = 0;
                                    // Continue the iteration.
                                    continue;
                                }
                            }
                            // Has the count been exceeded? Is there more text coming?
                            if(lCount >= (lSeparationSize + 1) && (i < pEditable.length())) {
                                // Reset the count.
                                lCount = 0;
                                // Insert the separator.
                                pEditable.insert(i, Character.toString(lSeparator));
                                // Increase the iteration count.
                                i++;
                            }
                        }
                    }
                    /** Unused overrides. */
                    @Override public final void beforeTextChanged(final CharSequence pCharSequence, final int pStart, final int pCount, final int pAfter) { }
                    @Override public final void onTextChanged(final CharSequence pCharSequence, final int pStart, final int pBefore, final int pCount) { }
                }
                

                或者,这里有一个基于epool's 实现的更简洁的实现。

                public final class TextGroupFormattingListener implements TextWatcher {
                
                    /* Member Variables. */
                    private final int    mGroupLength;
                    private final String mSeparator;
                    private       String mSource;
                
                    /** Constructor. */
                    public TextGroupFormattingListener(final String pSeparator, final int pGroupLength) {
                        // Initialize Member Variables.
                        this.mSeparator   = pSeparator;
                        this.mGroupLength = pGroupLength;
                        this.mSource      = "";
                    }
                
                    /** Formats the Field to display user-friendly separation of the input values. */
                    @Override public final void afterTextChanged(final Editable pEditable) {
                        // Fetch the Source.
                        String lSource = pEditable.toString();
                        // Has the text changed?
                        if (!this.getSource().equals(lSource)) {
                            // Remove all of the existing Separators.
                            lSource = lSource.replace(this.getSeparator(), "");
                            // Allocate a StringBuilder.
                            StringBuilder lStringBuilder = new StringBuilder();
                            // Iterate across the Source String, which contains the raw user input.
                            for(int i = 0; i < lSource.length(); i++) {
                                // Have we exceeded the GroupLength?
                                if(i > 0 && i % this.getGroupLength() == 0) {
                                    // Append the separator.
                                    lStringBuilder.append(this.getSeparator());
                                }
                                // Append the user's character data.
                                lStringBuilder.append(lSource.charAt(i));
                            }
                            // Track changes to the Source.
                            this.setSource(lStringBuilder.toString());
                            // Replace the contents of the Editable with this new String.
                            pEditable.replace(0, pEditable.length(), this.getSource());
                        }
                    }
                
                    /** Unused overrides. */
                    @Override public final void beforeTextChanged(final CharSequence pCharSequence, final int pStart, final int pCount, final int pAfter) { }
                    @Override public final void onTextChanged(final CharSequence pCharSequence, final int pStart, final int pBefore, final int pCount)    { }
                
                    public final int getGroupLength() {
                        return this.mGroupLength;
                    }
                
                    public final String getSeparator() {
                        return this.mSeparator;
                    }
                
                    private final void setSource(final String pSource) {
                        this.mSource = pSource;
                    }
                
                    private final String getSource() {
                        return this.mSource;
                    }
                
                }
                

                【讨论】:

                  【解决方案19】:

                  以上答案都不适合我。我创建了一个解决 start-string/end-string/mid-string 问题的解决方案。复制和粘贴也应该可以正常工作。这支持万事达卡、维萨卡和美国运通卡。您可以更改分隔符。如果您不需要付款方式类型,只需将其删除。不过是 Kotlin。这个想法很简单。每次文本更改时,我都会删除所有分隔符并根据格式重新添加它们。解决了 start-string/mid-string 问题。那么唯一的问题是你需要在添加分隔符后计算出正确的文本位置。

                  fun addCreditCardNumberTxtWatcher(et: EditText, separator: Char, paymentMethodType: PaymentMethodType): TextWatcher {
                      val tw = object : TextWatcher {
                          var mBlock = false
                          override fun afterTextChanged(s: Editable) {
                          }
                          override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                                Logger.d("_debug", "s: $s, start: $start, count: $count, after $after")
                          }
                          override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                              if (mBlock)
                                  return
                              var lastPos = et.selectionStart
                              val oldStr = et.text.toString().replace(separator.toString(), "", false)
                              var newFormattedStr = ""
                              if (before > 0) {
                                  if (lastPos > 0 && et.text.toString()[lastPos - 1] == separator) lastPos--
                              }
                              Logger.d("_debug", "lastPos: $lastPos, s: $s, start: $start, before: $before, count $count")
                              mBlock = true
                              oldStr.forEachIndexed { i, c ->
                                  when (paymentMethodType) {
                                      PaymentMethodType.MASTERCARD, PaymentMethodType.VISA -> {
                                          if (i > 0 && i % 4 == 0) {
                                              newFormattedStr += separator
                                          }
                                      }
                                      PaymentMethodType.AMERICAN_EXPRESS -> {
                                          if (i == 4 || i == 10 || i == 15) {
                                              newFormattedStr += separator
                                          }
                                      }
                                  }
                                  newFormattedStr += c
                              }
                              et.setText(newFormattedStr)
                              if (before == 0) {
                                  if (et.text.toString()[lastPos - 1] == separator) lastPos++
                              }
                              et.setSelection(lastPos)
                              mBlock = false
                          }
                      }
                      et.addTextChangedListener(tw)
                      return tw
                  }
                  

                  【讨论】:

                  • 完美运行!不过,我建议不要使用可能会改变的付款方式类型(美国运通可能会采用不同的格式)。我建议使用必须存在分隔符的索引列表。例如,对于长度通常为 16 的卡片,分隔符将是 listOf(4, 9, 13)。对于长度通常为 15 的卡片,分隔符将是 listOf(4, 10)
                  【解决方案20】:

                  这个解决方案是为IBAN实现的,但原理是一样的,我试图纠正上面答案中的所有主要问题,如果你发现错误,请随时说出来,谢谢。

                  设置EditText并限制可以使用的字符:

                  private void setEditTextIBAN(View view) {
                      editTextIBAN = (EditText) view.findViewById(R.id.client_iban);
                      editTextIBAN.setKeyListener(
                              DigitsKeyListener.getInstance("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 "));
                      editTextIBAN.addTextChangedListener(new IBANTextWatcher());
                  }
                  

                  这是 TextWatcher:

                  private class IBANTextWatcher implements TextWatcher {
                  
                      // means divider position is every 5th symbol
                      private static final int DIVIDER_MODULO = 5;
                      private static final int GROUP_SIZE = DIVIDER_MODULO - 1;
                      private static final char DIVIDER = ' ';
                      private static final String STRING_DIVIDER = " ";
                      private String previousText = "";
                  
                      private int deleteLength;
                      private int insertLength;
                      private int start;
                  
                      private String regexIBAN = "(\\w{" + GROUP_SIZE + "}" + DIVIDER +
                              ")*\\w{1," + GROUP_SIZE + "}";
                      private Pattern patternIBAN = Pattern.compile(regexIBAN);
                  
                      @Override
                      public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
                          this.previousText = s.toString();
                          this.deleteLength = count;
                          this.insertLength = after;
                          this.start = start;
                      }
                  
                      @Override
                      public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
                  
                      }
                  
                      @Override
                      public void afterTextChanged(final Editable s) {
                          String originalString = s.toString();
                  
                          if (!previousText.equals(originalString) &&
                                  !isInputCorrect(originalString)) {
                              String newString = previousText.substring(0, start);
                              int cursor = start;
                  
                              if (deleteLength > 0 && s.length() > 0 &&
                                      (previousText.charAt(start) == DIVIDER ||
                                              start == s.length())) {
                                  newString = previousText.substring(0, start - 1);
                                  --cursor;
                              }
                  
                              if (insertLength > 0) {
                                  newString += originalString.substring(start, start + insertLength);
                                  newString = buildCorrectInput(newString);
                                  cursor = newString.length();
                              }
                  
                              newString += previousText.substring(start + deleteLength);
                              s.replace(0, s.length(), buildCorrectInput(newString));
                  
                              editTextIBAN.setSelection(cursor);
                          }
                      }
                  
                      /**
                       * Check if String has the white spaces in the correct positions, meaning
                       * if we have the String "123456789" and there should exist a white space
                       * every 4 characters then the correct String should be "1234 5678 9".
                       *
                       * @param s String to be evaluated
                       * @return true if string s is written correctly
                       */
                      private boolean isInputCorrect(String s) {
                          Matcher matcherDot = patternIBAN.matcher(s);
                          return matcherDot.matches();
                      }
                  
                      /**
                       * Puts the white spaces in the correct positions,
                       * see the example in {@link IBANTextWatcher#isInputCorrect(String)}
                       * to understand the correct positions.
                       *
                       * @param s String to be corrected.
                       * @return String corrected.
                       */
                      private String buildCorrectInput(String s) {
                          StringBuilder sbs = new StringBuilder(
                                  s.replaceAll(STRING_DIVIDER, ""));
                  
                          // Insert the divider in the correct positions
                          for (int i = GROUP_SIZE; i < sbs.length(); i += DIVIDER_MODULO) {
                              sbs.insert(i, DIVIDER);
                          }
                  
                          return sbs.toString();
                      }
                  }
                  

                  【讨论】:

                    【解决方案21】:

                    如果有人还在寻找答案,

                    尝试使用 format-edit-text 库在一行代码中自动设置文本格式。这个库使用破折号来定义输入的格式。

                    editText.setFormat("any (dash) format");
                    

                    如何使用

                    在 app/build.gradle 中添加 format-edit-text 库依赖

                    implementation 'com.androidwidgets:formatedittext:0.2.0'
                    

                    在activity_main.xml中添加FormatEditText视图

                    <?xml version="1.0" encoding="utf-8"?>
                    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
                        xmlns:app="http://schemas.android.com/apk/res-auto"
                        xmlns:tools="http://schemas.android.com/tools"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        tools:context=".MainActivity"
                        android:focusableInTouchMode="true"
                        android:focusable="true">
                    
                        <com.androidwidgets.formatedittext.widgets.FormatEditText
                            android:id="@+id/edit_text_1"
                            android:layout_width="0dp"
                            android:layout_height="wrap_content"
                            android:layout_marginStart="16dp"
                            android:layout_marginLeft="16dp"
                            android:layout_marginTop="16dp"
                            android:layout_marginEnd="16dp"
                            android:layout_marginRight="16dp"
                            android:imeOptions="actionSend"
                            android:inputType="number"
                            app:layout_constraintEnd_toEndOf="parent"
                            app:layout_constraintHorizontal_bias="0.0"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintTop_toTopOf="parent" />
                    
                    </androidx.constraintlayout.widget.ConstraintLayout>
                    

                    在 MainActivity.java 中将信用卡格式设置为 FormatEditText 视图

                    public class MainActivity extends AppCompatActivity {
                    
                        @Override
                        protected void onCreate(Bundle savedInstanceState) {
                            super.onCreate(savedInstanceState);
                            setContentView(R.layout.activity_main);
                    
                            final FormatEditText editText1 = findViewById(R.id.edit_text_1);
                            editText1.setFormat("---- ---- ---- ----");
                        }
                    }
                    

                    这将产生以下输出

                    PS:请确保将参数 inputType 添加到布局文件中的 FormatEditText 视图中。

                    android:inputType="number"
                    

                    【讨论】:

                      【解决方案22】:

                      我知道这个问题有点老了,但我需要为 IBAN 实施这个问题,并且对给定的答案不满意。所以我为此写了一些代码。但它以“模式”和“分隔符”为参数,因此也可以用于信用卡号。

                      这是扩展的文本观察器类。

                      import android.text.Editable;
                      import android.text.TextWatcher;
                      import android.widget.EditText;
                      
                      public class IbanTextWatcher implements TextWatcher {
                      
                          private int[] pattern;
                          private String divider;
                          private String before;
                          private EditText field;
                          private boolean dividerDeleted;
                          
                      
                          public IbanTextWatcher(int[] pattern, String divider, EditText field) {
                              this.divider = divider;
                              this.pattern = pattern;
                              this.field = field;
                          }
                      
                      
                          @Override
                          public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                              before = charSequence.toString();
                      
                              if (!String.valueOf(charSequence).equals("") && charSequence.length() > i) {
                                  if (String.valueOf(before.charAt(i)).equals(getDivider())) {
                                      dividerDeleted = true;
                                  } else {
                                      dividerDeleted = false;
                                  }
                              }
                          }
                      
                          @Override
                          public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                      
                          }
                      
                          @Override
                          public void afterTextChanged(Editable editable) {
                      
                              String input = editable.toString().replaceAll("\\s", "");
                              StringBuilder output = new StringBuilder();
                              boolean error = false;
                              int currentIndex = 0;
                              int cursorPosition = getField().getSelectionStart();
                              int lengthBefore;
                              int currentPatternMember = 0;
                              
                              //prevent user to delete the divider
                              if (dividerDeleted && cursorPosition != getField().getText().length()) {
                                  getField().setText(getBefore());
                                  getField().setSelection(cursorPosition + 1);
                                  return;
                              } else if (input.equals(getBefore().replaceAll("\\s", ""))) {
                                  return;
                              }
                      
                              for (int i = 0; i < getPattern().length; i++) {
                                  error = false;
                                  currentPatternMember = getPattern()[i];
                                  try {
                                      output.append(input.substring(currentIndex, currentIndex + currentPatternMember));
                                  } catch (StringIndexOutOfBoundsException e) {
                                      error = true;
                                  }
                      
                                  if (!error) {
                                      if (i != getPattern().length - 1) {
                                          output.append(getDivider());
                                      }
                                      currentIndex += currentPatternMember;
                                  } else {
                                      break;
                                  }
                              }
                      
                              if (error) {
                                  output.append(input.substring(currentIndex, input.length()));
                              } 
                      
                              cursorPosition = getField().getSelectionStart();
                              lengthBefore = getBefore().length();
                              getField().setText(output.toString());
                      
                              if (cursorPosition != lengthBefore && cursorPosition != lengthBefore + 1) {
                                  getField().setSelection(cursorPosition);
                              } else {
                                  getField().setSelection(getField().getText().length());
                              }
                          }
                      
                          public int[] getPattern() {
                              return pattern;
                          }
                      
                          public String getDivider() {
                              return divider;
                          }
                      
                          public String getBefore() {
                              return before;
                          }
                      
                          public EditText getField() {
                              return field;
                          }
                      }
                      

                      这就是我使用它的方式:

                      int[] pattern = {2,4,4,4,4,4,2}; // 
                      iban.addTextChangedListener(new IbanTextWatcher(pattern, " ", iban)); //here iban is my edittext field
                      

                      顺便说一下,我在xml中设置了字段的最大长度。

                      【讨论】:

                      • 很惊讶我找不到一个完善的库来处理 UI 中的 ibans ......这工作正常,但我可能会添加一些改进(显示字母大写,禁止特殊字符......)
                      • 在空格删除空格而不是最后一个输入后也删除。
                      【解决方案23】:

                      在您的布局中:

                          <android.support.design.widget.TextInputEditText
                              android:id="@+id/et_credit_card_number"
                              android:digits=" 1234567890"
                              android:inputType="number"
                              android:maxLength="19"/>
                      

                      这里是TextWachter,它在 16 位信用卡的每 4 位数字上设置一个空格。

                      class CreditCardFormatWatcher : TextWatcherAdapter() {
                      
                          override fun afterTextChanged(s: Editable?) {
                              if (s == null || s.isEmpty()) return
                      
                              s.forEachIndexed { index, c ->
                                  val spaceIndex = index == 4 || index == 9 || index == 14
                                  when {
                                      !spaceIndex && !c.isDigit()     -> s.delete(index, index + 1)
                                      spaceIndex && !c.isWhitespace() -> s.insert(index, " ")
                                  }
                              }
                      
                              if (s.last().isWhitespace())
                                  s.delete(s.length - 1, s.length)
                          }
                      
                      }
                      

                      【讨论】:

                        【解决方案24】:
                         private class TextWatcherIBAN implements TextWatcher {
                        
                                @Override
                                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                        
                                }
                        
                                @Override
                                public void onTextChanged(CharSequence s, int start, int before, int count) {
                        
                                }
                        
                                @Override
                                public void afterTextChanged(Editable s) {
                                    textInputEditText.removeTextChangedListener(this);
                                    formatIBANEditText(textInputEditText);
                                    textInputEditText.addTextChangedListener(this);
                        
                                }
                            }
                        
                        
                        public void formatIBANEditText(TextInputEditText editText) {
                            String decimalAmount = editText.getText().toString();
                            int selection = editText.getSelectionEnd() == decimalAmount.length() ? -1 : editText.getSelectionEnd();
                            decimalAmount = formatIBAN(decimalAmount);
                            editText.setText(decimalAmount);
                        
                            if (selection != -1) {
                                editText.setSelection(selection);
                            } else {
                                editText.setSelection(decimalAmount.length());
                            }
                        
                        }
                        
                        public String formatIBAN(String text) {
                            return formatterIBAN(new StringBuilder(text));
                        }
                        
                        private String formatterIBAN(StringBuilder text) {
                            int group = text.toString().length() / 5;
                            int spaceCount = getSpaceCount(text);
                            if (spaceCount < group) {
                                return formatterIBAN(text.insert(4 + 5 * spaceCount, space));
                            } else {
                                return text.toString();
                            }
                        }
                        
                        private int getSpaceCount(StringBuilder text) {
                            int spaceCount = 0;
                            for (int index = 0; index < text.length(); index++) {
                                if (text.charAt(index) == space.charAt(0)) {
                                    spaceCount++;
                                }
                            }
                            return spaceCount;
                        }
                        
                        
                        textInputEditText.addTextChangedListener(new TextWatcherIBAN());
                        

                        【讨论】:

                          【解决方案25】:
                          class XYZ : TextWatcher {
                          
                          private val formatSymbols = DecimalFormatSymbols(Locale.getDefault())
                          
                          private lateinit var formatter: DecimalFormat
                          
                          override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
                              .
                              .
                              formatSymbols.groupingSeparator = ' '
                              formatter = DecimalFormat("####,####", formatSymbols)
                              .
                              .
                          }
                          
                          override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                              super.onViewCreated(view, savedInstanceState)
                              editText.addTextChangedListener(this)
                          }
                          
                          override fun afterTextChanged(s: Editable?) {
                              if (editText.error != null) {
                                  editText.error = null
                              }
                              editText.removeTextChangedListener(this)
                              try {
                                  var originalString = s.toString()
                                  if (originalString.contains(" ")) {
                                      originalString = originalString.replace(" ", "", true)
                                  }
                                  val longVal: Long? = originalString.toLong()
                                  val formattedString = formatter.format(longVal)
                                  editText.setText(formattedString)
                                  editText.setSelection(editText.text.length)
                              } catch (error: NumberFormatException) {
                                  // Print Error Or Do Whatever you want.
                              }
                              editText.addTextChangedListener(this)
                          }
                          
                          }
                          

                          【讨论】:

                            【解决方案26】:

                            这是我基于Igor Tyulkanov's idea的实现,它有一个小的改进,解决了光标位置问题

                            class CardNumbersInputWatcher(private val editText: EditText) : TextWatcher {
                              companion object {
                                private const val TOTAL_SYMBOLS = 19
                                private const val DIVIDER_DISTANCE = 4
                                private const val DIVIDER = ' '
                              }
                            
                              override fun afterTextChanged(s: Editable) {
                                if (!isInputCorrect(s, TOTAL_SYMBOLS, DIVIDER_DISTANCE, DIVIDER)) {
                                  val beforeCurPos = editText.selectionStart
                                  val beforeLength = s.length
                                  s.replace(0, s.length, buildCorrectString(s, TOTAL_SYMBOLS, DIVIDER_DISTANCE, DIVIDER))
                                  if (beforeLength > TOTAL_SYMBOLS && beforeCurPos <= s.length && editText.selectionStart < beforeCurPos) {
                                    editText.setSelection(beforeCurPos)
                                  }
                                }
                              }
                            
                              override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
                              override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
                            }
                            
                            private fun isInputCorrect(s: Editable, totalSymbols: Int, dividerDistance: Int, divider: Char): Boolean {
                              if (s.length > totalSymbols) {
                                return false
                              }
                              return s.withIndex().all { (index, c) ->
                                if (index != 0 && ((index + 1) % (dividerDistance + 1) == 0)) {
                                  // it should be divider
                                  c == divider
                                } else {
                                  c.isDigit()
                                }
                              }
                            }
                            
                            private fun buildCorrectString(s: Editable, totalSymbols: Int, dividerDistance: Int, divider: Char): String {
                              return buildString {
                                for (c in s) {
                                  if (length >= totalSymbols) break
                                  if (!c.isDigit()) continue
                                  if (length > 0 && ((length + 1) % (dividerDistance + 1)) == 0) append(divider)
                                  append(c)
                                }
                              }
                            }
                            
                            

                            【讨论】:

                            • 获取java.lang.StackOverflowError: stack size 8192KB
                            【解决方案27】:

                            1.复制并粘贴此类

                            class EditTextForCards @JvmOverloads constructor(
                                context: Context,
                                attrs: AttributeSet? = null,
                                defStyleAttr: Int = androidx.appcompat.R.attr.editTextStyle
                            ) : AppCompatEditText(context, attrs, defStyleAttr) {
                            
                                private var mCCPatterns = SparseArray<Pattern>()
                                private var mSeparator: Separator = Separator.NONE
                                private var mDrawableGravity: Gravity? = null/*Gravity.END*/
                                private var isValidCard: Boolean = false
                                private var mCurrentDrawableResId = Card.UNKNOWN.drawableRes
                            
                                val textWithoutSeparator
                                    get() = if (mSeparator == Separator.NONE) {
                                        text.toString()
                                    } else {
                                        text.toString().replace(mSeparator.toRegex(), "")
                                    }
                            
                                val isCardValid: Boolean
                                    get() = textWithoutSeparator.length > 12 && isValidCard
                            
                                val cardType: Card
                                    get() = Card.from(mCurrentDrawableResId)
                            
                                enum class Separator(private val stringValue: String) {
                                    NONE(""), SPACES(" "), DASHES("-");
                            
                                    override fun toString() = stringValue
                            
                                    internal fun toRegex() = stringValue.toRegex()
                            
                                    internal val length
                                        get() = stringValue.length
                                }
                            
                                enum class Gravity {
                                    START, END, LEFT, RIGHT
                                }
                            
                                enum class Card(internal val value: Int, @field:DrawableRes internal val drawableRes: Int) {
                                    VISA(1, R.drawable.ic_visa),
                                    MASTERCARD(2, R.drawable.ic_mastercard),
                                    AMEX(4, R.drawable.amex),
                                    DISCOVER(8, R.drawable.discover),
                                    UNKNOWN(-1, R.drawable.ic_visa);
                            
                                    companion object {
                                        internal fun from(@DrawableRes drawableRes: Int): Card {
                                            for (card in values()) {
                                                if (card.drawableRes == drawableRes) {
                                                    return card
                                                }
                                            }
                                            return UNKNOWN
                                        }
                                    }
                                }
                            
                                private val textWatcher = object : TextWatcher {
                                    override fun afterTextChanged(s: Editable?) {}
                            
                                    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
                            
                                    override fun onTextChanged(
                                        text: CharSequence,
                                        start: Int,
                                        lengthBefore: Int,
                                        lengthAfter: Int
                                    ) {
                                        val textWithoutSeparator = textWithoutSeparator
                            
                                        var mDrawableResId = 0
                                        for (i in 0 until mCCPatterns.size()) {
                                            val key = mCCPatterns.keyAt(i)
                            
                                            val p = mCCPatterns.get(key)
                            
                                            val m = p.matcher(textWithoutSeparator)
                                            isValidCard = m.find()
                                            if (isValidCard) {
                                                mDrawableResId = key
                                                break
                                            }
                                        }
                            //            if (mDrawableResId != 0 && mDrawableResId != mCurrentDrawableResId) {
                            //                mCurrentDrawableResId = mDrawableResId
                            //            } else if (mDrawableResId == 0) {
                            //                mCurrentDrawableResId = Card.UNKNOWN.drawableRes
                            //            }
                            //            addDrawable()
                                        addSeparators()
                                    }
                                }
                            
                                init {
                                    setDisabledCards()
                                    inputType = InputType.TYPE_CLASS_PHONE
                                    setSeparator(Separator.NONE)
                            //        setDrawableGravity(Gravity.END)
                                    attrs?.let { applyAttributes(it) }
                                    addTextChangedListener(textWatcher)
                                }
                            
                                private fun applyAttributes(attrs: AttributeSet) {
                                    val a = context.theme.obtainStyledAttributes(
                                        attrs,
                                        R.styleable.EditTextForCards,
                                        0, 0
                                    )
                                    try {
                                        setSeparator(
                                            Separator.values()[a.getInt(
                                                R.styleable.EditTextForCards_separator,
                                                Separator.NONE.ordinal
                                            )]
                                        )
                                        setDisabledCardsInternal(a.getInt(R.styleable.EditTextForCards_disabledCards, 0))
                                        setDrawableGravity(
                                            Gravity.values()[a.getInt(
                                                R.styleable.EditTextForCards_drawableGravity,
                                                Gravity.END.ordinal
                                            )]
                                        )
                                    } finally {
                                        a.recycle()
                                    }
                                }
                            
                                private fun addDrawable() {
                                    var currentDrawable = ContextCompat.getDrawable(context, mCurrentDrawableResId)
                                    if (currentDrawable != null && error.isNullOrEmpty()) {
                                        currentDrawable = resize(currentDrawable)
                                        when (mDrawableGravity) {
                                            Gravity.START -> setDrawablesRelative(start = currentDrawable)
                                            Gravity.RIGHT -> setDrawables(right = currentDrawable)
                                            Gravity.LEFT -> setDrawables(left = currentDrawable)
                                            else -> setDrawablesRelative(end = currentDrawable)
                                        }
                                    }
                                }
                            
                                private fun addSeparators() {
                                    val text = text.toString()
                                    if (mSeparator != Separator.NONE) {
                                        if (text.length > 4 && !text.matches("(?:[0-9]{4}$mSeparator)+[0-9]{1,4}".toRegex())) {
                                            val sp = StringBuilder()
                                            val caretPosition = selectionEnd
                                            val segments = splitString(text.replace(mSeparator.toRegex(), ""))
                                            for (segment in segments) {
                                                sp.append(segment).append(mSeparator)
                                            }
                                            setText("")
                                            append(sp.delete(sp.length - mSeparator.length, sp.length).toString())
                                            if (caretPosition < text.length)
                                                setSelection(caretPosition)
                                        }
                                    }
                                }
                            
                                private fun removeSeparators() {
                                    var text = text.toString()
                                    text = text.replace(" ".toRegex(), "").replace("-".toRegex(), "")
                                    setText("")
                                    append(text)
                                }
                            
                                private fun splitString(s: String): Array<String?> {
                                    val arrayLength = ceil(s.length / 4.toDouble()).toInt()
                                    val result = arrayOfNulls<String>(arrayLength)
                            
                                    var j = 0
                                    val lastIndex = result.size - 1
                                    for (i in 0 until lastIndex) {
                                        result[i] = s.substring(j, j + 4)
                                        j += 4
                                    }
                                    result[lastIndex] = s.substring(j)
                            
                                    return result
                                }
                            
                                /*@Deprecated("Please use the method that accepts a Separator enum instead.", ReplaceWith("this.setSeparator(Separator.)"))
                                fun setSeparator(@IntRange(from = 0, to = 2) separator: Int) {
                                    require(!(separator > 2 || separator < 0)) {
                                        "The separator has to be one of the following:" +
                                                "NO_SEPARATOR." +
                                                "SPACES_SEPARATOR." +
                                                "DASHES_SEPARATOR."
                                    }
                                    setSeparator(Separator.values()[separator])
                                }*/
                            
                                /**
                                 * Use this method to set the separator style.
                                 * The default separator is [Separator.NONE].
                                 *
                                 * @param separator the style of the separator.
                                 */
                                fun setSeparator(separator: Separator) {
                                    mSeparator = separator
                                    if (mSeparator != Separator.NONE) {
                                        filters = arrayOf<InputFilter>(InputFilter.LengthFilter(23))
                                        keyListener = DigitsKeyListener.getInstance("0123456789$mSeparator")
                                        addSeparators()
                                    } else {
                                        filters = arrayOf<InputFilter>(InputFilter.LengthFilter(19))
                                        keyListener = DigitsKeyListener.getInstance("0123456789")
                                        removeSeparators()
                                    }
                                }
                            
                                /**
                                 * Use this method to set the location of the card drawable.
                                 * The default gravity is [Gravity.END].
                                 *
                                 * @param gravity the drawable location.
                                 */
                                fun setDrawableGravity(gravity: Gravity) {
                                    mDrawableGravity = gravity
                                    addDrawable()
                                }
                            
                                private fun setDisabledCardsInternal(disabledCards: Int) {
                                    val cards = ArrayList<Card>()
                                    if (containsFlag(disabledCards, Card.VISA.value)) {
                                        cards.add(Card.VISA)
                                    }
                                    if (containsFlag(disabledCards, Card.MASTERCARD.value)) {
                                        cards.add(Card.MASTERCARD)
                                    }
                                    /*if (containsFlag(disabledCards, Card.AMEX.value)) {
                                        cards.add(Card.AMEX)
                                    }
                                    if (containsFlag(disabledCards, Card.DISCOVER.value)) {
                                        cards.add(Card.DISCOVER)
                                    }*/
                                    setDisabledCards(*cards.toTypedArray())
                                }
                            
                                @Deprecated(
                                    "Please use the method that accepts an array of Cards instead.",
                                    ReplaceWith("this.setDisabledCards(cards)")
                                )
                                fun setDisabledCards(disabledCards: Int) {
                                    setDisabledCardsInternal(disabledCards)
                                }
                            
                                /**
                                 * Use this method to set which cards are disabled.
                                 * By default all supported cards are enabled.
                                 *
                                 * @param cards the cards to be disabled.
                                 */
                                fun setDisabledCards(vararg cards: Card) {
                                    var disabledCards = 0
                                    for (card in cards) {
                                        disabledCards = disabledCards or card.value
                                    }
                                    mCCPatterns.clear()
                                    if (!containsFlag(disabledCards, Card.VISA.value)) {
                                        mCCPatterns.put(Card.VISA.drawableRes, Pattern.compile("^4[0-9]{1,12}(?:[0-9]{6})?$"))
                                    }
                                    if (!containsFlag(disabledCards, Card.MASTERCARD.value)) {
                                        mCCPatterns.put(Card.MASTERCARD.drawableRes, Pattern.compile("^5[1-5][0-9]{0,14}$"))
                                    }
                                    /*if (!containsFlag(disabledCards, Card.AMEX.value)) {
                                        mCCPatterns.put(Card.AMEX.drawableRes, Pattern.compile("^3[47][0-9]{0,13}$"))
                                    }
                                    if (!containsFlag(disabledCards, Card.DISCOVER.value)) {
                                        mCCPatterns.put(Card.DISCOVER.drawableRes, Pattern.compile("^6(?:011|5[0-9]{1,2})[0-9]{0,12}$"))
                                    }*/
                                    textWatcher.onTextChanged("", 0, 0, 0)
                                }
                            
                                private fun containsFlag(flagSet: Int, flag: Int): Boolean {
                                    return flagSet or flag == flagSet
                                }
                            
                                override fun onDraw(canvas: Canvas) {
                                    super.onDraw(canvas)
                                    var noDrawablesVisible = true
                                    for (drawable in compoundDrawables) {
                                        if (drawable != null) {
                                            noDrawablesVisible = false
                                            break
                                        }
                                    }
                                    if (noDrawablesVisible) {
                                        addDrawable()
                                    }
                                }
                            
                                private fun resize(image: Drawable) =
                                    when (val height = measuredHeight - (paddingTop + paddingBottom)) {
                                        in 1 until image.intrinsicHeight -> {
                                            val bitmap = (image as BitmapDrawable).bitmap
                                            val ratio = image.getIntrinsicWidth().toFloat() / image.intrinsicHeight.toFloat()
                                            val resizedBitmap =
                                                Bitmap.createScaledBitmap(bitmap, (height * ratio).toInt(), height, false)
                                            resizedBitmap.density = Bitmap.DENSITY_NONE
                                            BitmapDrawable(resources, resizedBitmap)
                                        }
                                        in Int.MIN_VALUE..0 -> null
                                        else -> image
                                    }
                            
                                private fun setDrawablesRelative(
                                    start: Drawable? = null,
                                    top: Drawable? = null,
                                    end: Drawable? = null,
                                    bottom: Drawable? = null
                                ) =
                                    /*TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, start, top, end, bottom)*/
                                    TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, null, null, null, null)
                            
                                private fun setDrawables(
                                    left: Drawable? = null,
                                    top: Drawable? = null,
                                    right: Drawable? = null,
                                    bottom: Drawable? = null
                                ) =
                                    /*setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom)*/
                                    setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
                            
                                companion object {
                                    @Deprecated("This constant has been replace with an enum.", ReplaceWith("Separator.NONE"))
                                    const val NO_SEPARATOR = 0
                            
                                    @Deprecated("This constant has been replace with an enum.", ReplaceWith("Separator.SPACES"))
                                    const val SPACES_SEPARATOR = 1
                            
                                    @Deprecated("This constant has been replace with an enum.", ReplaceWith("Separator.DASHES"))
                                    const val DASHES_SEPARATOR = 2
                            
                                    @Deprecated("This constant has been replace with an enum.", ReplaceWith("null"))
                                    const val NONE = 0
                            
                                    @Deprecated("This constant has been replace with an enum.", ReplaceWith("Card.VISA"))
                                    const val VISA = 1
                            
                                    @Deprecated("This constant has been replace with an enum.", ReplaceWith("Card.MASTERCARD"))
                                    const val MASTERCARD = 2
                            
                                    @Deprecated("This constant has been replace with an enum.", ReplaceWith("Card.AMEX"))
                                    const val AMEX = 4
                            
                                    @Deprecated("This constant has been replace with an enum.", ReplaceWith("Card.DISCOVER"))
                                    const val DISCOVER = 8
                                }
                            }
                            

                            2。粘贴此样式

                            <declare-styleable name="EditTextForCards">
                                <attr name="separator" format="enum">
                                    <enum name="no_separator" value="0" />
                                    <enum name="spaces" value="1" />
                                    <enum name="dashes" value="2" />
                                </attr>
                                <attr name="disabledCards">
                                    <flag name="none" value="0" />
                                    <flag name="visa" value="1" />
                                    <flag name="mastercard" value="2" />
                                    <flag name="amex" value="4" />
                                    <flag name="discover" value="8" />
                                </attr>
                                <attr name="drawableGravity">
                                    <enum name="start" value="0" />
                                    <enum name="end" value="1" />
                                    <enum name="left" value="2" />
                                    <enum name="right" value="3" />
                                </attr>
                            </declare-styleable>
                            

                            3.在您的布局文件中,通过

                            使用它
                            <EditTextForCards
                                    android:layout_width="match_parent"
                                    android:layout_height="wrap_content"
                                    android:layout_margin="@dimen/dp_5"
                                    android:digits="0123456789 "
                                    android:hint="@string/card_number"
                                    android:padding="@dimen/dp_20"
                                    android:textColor="@android:color/white"
                                    android:textColorHint="@android:color/white"
                                    android:textSize="@dimen/sp_16"
                                    app:separator="spaces" />
                            

                            【讨论】:

                              【解决方案28】:
                              import android.text.Editable;
                              import android.text.TextWatcher;
                              import android.widget.EditText;`
                              
                              public class CreditCard implements TextWatcher
                              {
                                  EditText editText;
                              
                                  public CreditCard(EditText editText)
                                  {
                                      this.editText = editText;
                                  }
                              
                                  @Override
                                  public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                              
                                  }
                              
                                  @Override
                                  public void onTextChanged(CharSequence s, int start, int before, int count) {
                              
                                  }
                              
                                  @Override
                                  public void afterTextChanged(Editable s) {
                                      try
                                      {
                                          editText.removeTextChangedListener(this);
                              
                                          String str = editText.getText().toString().replaceAll("-", "");
                              
                                          editText.setText(setDash(str));
                              
                                          editText.setSelection(editText.getText().toString().length());
                              
                                          editText.addTextChangedListener(this);
                                          return;
                                      }
                              
                                      catch (Exception ex)
                                      {
                                          ex.printStackTrace();
                                          editText.addTextChangedListener(this);
                                      }
                              
                                  }
                              
                                  public static String setDash(String value)
                                  {
                                      String str = "";
                                      int j = 0;
                              
                                      for (int i = 0;i<value.length(); i++)
                                      {
                                          j++;
                              
                                          if (j == 5)
                                          {
                                              str = str+"-";
                                              j = 1;
                                          }
                              
                                          str = str + value.charAt(i);
                                      }
                              
                                      return str;
                              
                                  }
                              
                                  public static String trimDashOfString(String string)
                                  {
                                      if (string.contains("-")) {
                                          return string.replace("-", "");
                                          } else {
                                          return string;
                                      }
                              
                                  }
                              }
                              

                              【讨论】:

                                猜你喜欢
                                • 2011-08-22
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2011-10-22
                                • 2023-03-11
                                • 2011-11-16
                                • 1970-01-01
                                • 2016-08-18
                                相关资源
                                最近更新 更多