Форматирование кредитной карты в редактировании текста в android
Как заставить EditText
принять ввод в формате:
4digit 4digit 4digit 4digit
Я попытался пользовательский формат редактирования ввода текста android, чтобы принять номер кредитной карты, но, к сожалению, я не смог удалить пробелы. Всякий раз, когда есть пробел, я не мог его удалить. Пожалуйста, помогите мне разобраться в этом вопросе.
20 ответов:
После нахождения нескольких ответов, которые являются "ОК". Я перешел к лучшему 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"
, это отключит ' - ' и ' ' chars, поэтому я рекомендую использовать,inputType="phone"
. Это позволяет использовать другие символы, но просто используйте пользовательский inputfilter и проблема решена.
Поздний ответ, но я думаю, что это может быть полезно для кого-то:
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; } });
Это прекрасно работает с редактированием начальной строки/конечной строки/средней строки, а такжевставить отлично работает.
Я модифицировал ответ Криса Дженкинса, чтобы сделать его более надежным. При этом, даже если пользователь редактирует середину текста, интервальные символы все равно вставляются (и автоматически удаляются в неправильных местах) правильно.
Чтобы это работало правильно, убедитесь, что атрибуты
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; } } });
Вот более чистое решение, использующее регулярные выражения. Хотя регулярные выражения могут быть неэффективными, в этом случае их будет достаточно, так как они обрабатывают строку не более 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); } } });
Я добавляю свое решение в список. Насколько мне известно, у него нет недостатка; вы можете редактировать в середине, удалять символы интервалов, копировать и вставлять в него и т. д.
Чтобы разрешить редактирование в любом месте строки и сохранить положение курсора, редактируемое пространство пересекается и все пробелы (если таковые имеются) удаляются один за другим. Затем в соответствующие позиции добавляются новые пробелы. Это гарантирует, что курсор будет перемещаться вместе с изменениями, внесенными в содержимое.
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; } }
Эта реализация обеспечивает правильное размещение символов интервала, даже если пользователь редактирует среднюю строку. Поддерживаются и другие символы, отображаемые на программной клавиатуре (например, тире), то есть пользователь не может их ввести. Одно улучшение, которое можно было бы сделать: эта реализация не позволяет удалять промежуточные символы в середине строки.
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.
Я только что сделал следующую реализацию и хорошо работает для меня, даже с вставкой и вводом нового текста в любом положении
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); } } }
Использование:
editText.addTextChangedListener(new CreditCardFormattingTextWatcher());
Не уверен, что 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
Надеюсь, это поможет !
После долгих поисков и не получив ни одного удовлетворительного ответа для удовлетворения своих потребностей, я закончил писать свою собственную функцию.
Вот пример форматирования введенных данных кредитной карты в зависимости от типа вводимой карты. В настоящее время им занимаются 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); }
}
Примечание: объявления, сделанные в константах.java выглядит следующим образом:public static final int MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD = 19; public static final int MAX_LENGTH_CARD_NUMBER_AMEX = 17;
Надеюсь, это поможет!
Возможно, вы уже догадались, но вот что я сделал. Единственный метод, который я должен был переопределить, был AfterTextChanged.
Проверьте, если форма кредитной карты уже действительна, базовый случай, чтобы предотвратить бесконечную рекурсию
Если форма недопустима, удалите все пробелы и скопируйте в другую строку, вставляя пробелы там, где это необходимо.
Затем просто замените редактируемую строку на новую.
Если вам нужен код для конкретного шага, не стесняйтесь спрашивать.
И Прити, причина, по которой вы не можете удалить пробелы, заключается в том, что вы не можете изменить текст в обратном вызове onTextChanged. С сайта разработчика:
Public abstract void onTextChanged (CharSequence s, int start, int before, int count) Добавлено в API level 1
Этот метод вызывается, чтобы уведомить вас о том, что в пределах s символы count, начинающиеся в начале, только что заменили старый текст, который имел длину раньше. Попытка внести изменения в s из этого обратного вызова является ошибкой.
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) { } }); }
Вот пример, который использует все функции соответствующим образом, чтобы принять решение. Код может быть немного длиннее, но он будет быстрее, так как он в основном использует функции, заданные значения (start, before, count ...). Этот пример добавляет " - " каждые 4 цифры и удаляет их также, когда пользователь использует backspace. кроме того, убедитесь, что курсор будет в конце.
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. } }
}
Вот мое решение. Мои комментарии должны содержать достаточно информации для разработчика 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; } } }
Вот простое и легко настраиваемое решение, использующее класс
TextWatcher
. Он может быть назначен вашемуEditText
с помощью методаaddTextChangedListener()
.В качестве альтернативы, здесь гораздо более чистая реализация, основанная на реализацииepool .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) { } }
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; } }
Я думаю, что мое решение может хорошо работать независимо от операции среднего текста или операции копирования-вставки.
Пожалуйста, смотрите код, как показано ниже,
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, чтобы избежать других символов в файле макета.
Надеюсь, что это будет полезно для вас.
Ни один из приведенных выше ответов не является идеальным для меня. Я создал один, который решает запустить-строки/конец строки/СЧ-строку вопросов. Копирование и вставка также должны работать нормально. Это поддерживает Mastercard, Visa и Amex. Вы можете изменить разделитель. Если вам не нужен тип способа оплаты, просто удалите его. Но это Котлин. Идея проста. Каждый раз, когда текст меняется, я удаляю все разделители и повторно добавляю их в соответствии с форматом. В решает пуск-строка проблема/середине строки вопросов. Тогда единственная проблема в том, что вам нужно выработать правильную позицию текста после добавления разделителей.
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 }
Это решение было реализовано для IBAN, но принцип тот же, я попытался исправить все основные проблемы в ответах выше, если вы найдете ошибку, не стесняйтесь сказать это, спасибо.
Задайте редактируемый текст и ограничьте символы, которые можно использовать:
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(); } }
В вашем макете:
<android.support.design.widget.TextInputEditText android:id="@+id/et_credit_card_number" android:digits=" 1234567890" android:inputType="number" android:maxLength="19"/>
Здесь
TextWachter
, который устанавливает пробел на каждые 4 цифры в 16-номерной кредитной карте.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) } }
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());