How to make EditText
accept input in format:
4digit 4digit 4digit 4digit
I tried Custom format edit text input android to acc
Here's a simple and easily customizable solution using the TextWatcher
class. It may be assigned to your EditText
using the addTextChangedListener()
method.
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) { }
}
Alternatively, here is a much cleaner implementation based on epool's implementation.
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;
}
}
I just did the next implementation and works well for me, even with pasting and typing new text in any position of the 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);
}
}
}
Usage: editText.addTextChangedListener(new CreditCardFormattingTextWatcher());
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) {
}
});
}
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;
}
}
});
I know this question is a bit old but I need an implemantation of this for IBAN's and not satisfied with the given answers. So I wrote some code for this. But it takes the "pattern" and "divider" as parameters so it can be use for credit card numbers too.
This is the extended text watcher class.
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;
}
}
And this is how I use it:
int[] pattern = {2,4,4,4,4,4,2}; //
iban.addTextChangedListener(new IbanTextWatcher(pattern, " ", iban); //here iban is my edittext field
By the way, I set the max length of the field in xml.
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.