Format credit card in edit text in android
After finding multiple answers that are 'OK'. I moved towards a better TextWatcher which is designed to work correctly and independently from the TextView
.
TextWatcher class is as follows:
/**
* 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));
}
}
}
}
Then add it to your TextView as you would any other TextWatcher
.
{
//...
mEditTextCreditCard.addTextChangedListener(new FourDigitCardFormatWatcher());
}
This will auto delete the space sensibly going back so the user can actually do less keystrokes when editing.
Caveat
If you are using inputType="numberDigit"
this will disable the '-' and ' ' chars, so I recommend using, inputType="phone"
. This enables other chars, but just use a custom inputfilter and problem solved.
Example on github.com
Late answer, but I guess it may helpful for somebody:
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;
}
});
this works perfectly with start-string/end-string/mid-string editing, also paste works perfectly.
I modified Chris Jenkins answer to make it more robust. With this, even if the user edits the middle of the text, the spacing characters are still inserted (and automatically removed on wrong places) correctly.
To make this work correctly, make sure the EditText
attributes are set as follows (note the space on digits
):
android:digits="01234 56789"
android:inputType="number"
android:maxLength="19"
Then here is the TextWatcher
you need. The anonymous class can also be made static since this is independent of the 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;
}
}
});
Here is a cleaner solution using regular expressions. Although regular expressions can be inefficient, they would be sufficient in this case since it's processing a string of at most 19 characters, even if the processing occurs after each key press.
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);
}
}
});