Created
July 1, 2015 16:52
-
-
Save abdyer/9505750b085e334b0f6d to your computer and use it in GitHub Desktop.
CreditCardEditText
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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