Skip to content

Instantly share code, notes, and snippets.

@abdyer
Created July 1, 2015 16:52
Show Gist options
  • Save abdyer/9505750b085e334b0f6d to your computer and use it in GitHub Desktop.
Save abdyer/9505750b085e334b0f6d to your computer and use it in GitHub Desktop.
CreditCardEditText
package org.andydyer.example;
import android.content.Context;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.widget.EditText;
/**
* An {@link android.widget.EditText} that applies an input mask for credit card numbers
*
* Based on http://stackoverflow.com/a/12300091/3704431 with added logic for formatting
* different card types and enforcing the appropriate max length.
*/
public class CreditCardEditText extends EditText {
private static final String REGEX_AMEX_PREFIX = "^3[47].*";
private static final String REGEX_DINERS_CLUB_PREFIX = "^30[0-5].*";
private static final int CARD_TYPE_AMEX = 0;
private static final int CARD_TYPE_DINERS_CLUB = 1;
private static final int CARD_TYPE_OTHER = 2;
private int cardType;
public CreditCardEditText(Context context) {
this(context, null);
}
public CreditCardEditText(Context context, AttributeSet attrs) {
super(context, attrs);
addTextChangedListener(textChangedListener);
}
/**
* Same as setText(), but removes the TextWatcher before and restores it after
* @param charSequence
*/
public void setTextUnwatched(CharSequence charSequence) {
removeTextChangedListener(textChangedListener);
setText(charSequence);
addTextChangedListener(textChangedListener);
}
private TextWatcher textChangedListener = new TextWatcher() {
private boolean spaceDeleted;
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// check if a space was deleted
CharSequence charDeleted = s.subSequence(start, start + count);
spaceDeleted = " ".equals(charDeleted.toString());
}
public void afterTextChanged(Editable editable) {
removeTextChangedListener(this);
cardType = getCardType(editable.toString());
// record cursor position as setting the text places the cursor at the end
int cursorPosition = getSelectionStart();
String withSpaces = formatText(editable);
setText(withSpaces);
// set cursor at the last position + spaces added since spaces are added before cursor
setSelection(cursorPosition + (withSpaces.length() - editable.length()));
// if a space was deleted, move the cursor before the space
if (spaceDeleted) {
setSelection(getSelectionStart() - 1);
spaceDeleted = false;
}
updateMaxLength();
addTextChangedListener(this);
}
};
private int getCardType(String number) {
if (number.matches(REGEX_AMEX_PREFIX)) {
return CARD_TYPE_AMEX;
}
if (number.matches(REGEX_DINERS_CLUB_PREFIX)) {
return CARD_TYPE_DINERS_CLUB;
}
return CARD_TYPE_OTHER;
}
private String formatText(CharSequence text) {
StringBuilder formatted = new StringBuilder();
int position = 0;
for (int i = 0; i < text.length(); i++) {
if (Character.isDigit(text.charAt(i))) {
if (shouldAppendSpace(position)) {
formatted.append(" ");
}
formatted.append(text.charAt(i));
position++;
}
}
return formatted.toString();
}
private boolean shouldAppendSpace(int position) {
if (position == 0) {
return false;
}
if (cardType == CARD_TYPE_AMEX || cardType == CARD_TYPE_DINERS_CLUB) {
return position == 4 || position == 10;
} else {
return position % 4 == 0;
}
}
private void updateMaxLength() {
int maxLengthWithSpaces;
if (cardType == CARD_TYPE_AMEX) {
maxLengthWithSpaces = 17;
} else if (cardType == CARD_TYPE_DINERS_CLUB) {
maxLengthWithSpaces = 16;
} else {
maxLengthWithSpaces = 19;
}
setFilters(new InputFilter[]{ new InputFilter.LengthFilter(maxLengthWithSpaces) });
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment