在android中编辑文本中的信用卡格式

Pre*_*thi 55 formatting android android-edittext

如何以EditText格式进行接受输入:

4digit 4digit 4digit 4digit 
Run Code Online (Sandbox Code Playgroud)

我试过自定义格式编辑文本输入android接受信用卡号,但遗憾的是我无法删除空格.每当有空间时,我都无法删除它.请帮我找出问题所在.

Chr*_*ins 86

找到多个"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));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后像其他任何一样将它添加到TextView中TextWatcher.

{
  //...
  mEditTextCreditCard.addTextChangedListener(new FourDigitCardFormatWatcher()); 
}
Run Code Online (Sandbox Code Playgroud)

这将自动删除合理返回的空间,以便用户在编辑时实际上可以减少击键次数.

警告

如果你使用inputType="numberDigit"它将禁用' - '和''字符,所以我建议使用,inputType="phone".这可以启用其他字符,但只需使用自定义输入过滤器并解决问题.

  • 我还想补充一点,你可以将inputType保持为数字并使用android:digit ="0123456789 _". (23认同)
  • 刚花了15分钟尝试使用android:digit ="0123456789 _"(如@mpellegr所述),没有运气,直到我在这里看到 - http://stackoverflow.com/questions/3402693/how-to-set- input-type-to-number -cnimal-but-also-allow-a它是"数字"而不是"数字".也许它会帮助别人. (23认同)
  • 这不支持中间字符串编辑...用户可以移动到字段的中间并添加/删除内容,间距将不正确. (9认同)

Igo*_*nov 61

演示 - 这是如何工作的

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;
        }
    });
Run Code Online (Sandbox Code Playgroud)

这与start-string/end-string/mid-string编辑完美配合,也可以完美粘贴.

  • 不能完全正常工作。如果输入数字,则将光标放回该字段的开头,然后键入“ 12345”,最后显示为“ 1234 65” (5认同)
  • 真棒!谢啦!还要检查`android:digits ="0123456789"`是否包含CARD_NUMBER_DIVIDER(提防末尾的空格),否则会因堆栈溢出而崩溃!再次感谢! (5认同)
  • @IgorTyulkanov,我将你的代码添加到我的应用程序中,我只能在EditText中添加4个数字,我忘记了什么? (3认同)

Ran*_*ku' 21

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

要使其正常工作,请确保EditText属性设置如下(注意空格digits):

android:digits="01234 56789"
android:inputType="number"
android:maxLength="19"
Run Code Online (Sandbox Code Playgroud)

那么这就是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;
            }
        }
    });
Run Code Online (Sandbox Code Playgroud)

  • 工作大多很好.它不允许在最终位置之前删除空格(它只是立即将空间放回). (2认同)

小智 16

这是一个使用正则表达式的清洁解决方案 虽然正则表达式可以是低效率的,他们会因为它的处理最多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);
        }

    }

});
Run Code Online (Sandbox Code Playgroud)

  • 你晚上睡得怎么样!?关闭支架! (5认同)

Egi*_*gis 12

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

在此处输入图片说明

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
                }
            }
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

使用示例

<?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>
Run Code Online (Sandbox Code Playgroud)


MW.*_*MW. 11

我正在将我的解决方案添加到列表中.据我所知,它没有任何缺点; 您可以在中间编辑,删除间距字符,复制并粘贴到其中等.

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Tom*_*Tom 5

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

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));
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

适用PasswordTransformationMethod于掩盖CC数字的适当实现.


epo*_*ool 5

我只是做了下一个实现,并且对我来说效果很好,即使在任何位置粘贴和键入新文本也是如此EditText.

要点文件

/**
 * 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);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

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


小智 5

不确定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
    }
}
Run Code Online (Sandbox Code Playgroud)

和长度过滤器(Android SDK中)相结合:

mEditCardNumber.setFilters(new InputFilter[]{
     new InputFilter.LengthFilter(24),
     new CreditCardInputFilter(),
});
Run Code Online (Sandbox Code Playgroud)

这个手柄输入的情况下和删除一个数字。

(!),但是,这不处理的情况下进行复制/一整串的粘贴,这应该在不同的输入过滤器类完成

希望能帮助到你 !

  • 有人在乎良好的做法和有效的工作! (2认同)

Ste*_*ano 5

如果您使用 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) {
    }
}
Run Code Online (Sandbox Code Playgroud)

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" />
Run Code Online (Sandbox Code Playgroud)

以及如何使用它:

etCardNumber.addTextChangedListener(CreditCardTextFormatter())
Run Code Online (Sandbox Code Playgroud)