Format Credit Card Number with Android TextWatcher
View more Tutorials:

Card Type
The issuing network
|
Length Number of allowable digits |
IIN Ranges The card number will always begin with ... |
Spacing Patterns How the digits and spaces are printed on the physical credit cards |
Visa (incl. VPay) | 13-19 | 4 | #### #### #### #### (4-4-4-4) Pattern not known for 13-15 and 17-19 digit cards. |
Visa Electron | 16 | 4026, 417500, 4405, 4508, 4844, 4913, 4917 | #### #### #### #### (4-4-4-4) |
American Express | 15 | 34, 37 | #### ###### ##### (4-6-5) |
MasterCard | 16 | 51‑55, 222100‑272099 |
#### #### #### #### (4-4-4-4) |
Diners Club Carte Blanche | 14 | 300‑305 | #### ###### #### (4-6-4) |
Diners Club International | 14 | 300‑305, 309, 36, 38‑39 |
#### ###### #### (4-6-4) |
Diners Club United States & Canada | 16 | 54, 55 | #### #### #### #### (4-4-4-4) |
Discover | 16 | 6011, 622126‑622925, 644‑649, 65 |
#### #### #### #### (4-4-4-4) |
JCB | 16 | 3528‑3589 | #### #### #### #### (4-4-4-4) |
UATP | 15 | 1 | #### ##### ###### (4-5-6) |
Dankort | 16 | 5019 | #### #### #### #### (4-4-4-4) |
InterPayment | 16-19 | 636 | #### #### #### #### (4-4-4-4) Pattern not known for 17-19 digit cards. |
Maestro | 12-19 | 500000‑509999, 560000‑589999, 600000‑699999 |
#### #### ##### (4-4-5) #### ###### ##### (4-6-5) #### #### #### #### (4-4-4-4) #### #### #### #### ### (4-4-4-4-3) Pattern not known for 12, 14, 17, and 18 digit cards. |
China UnionPay | 16-19 | 62 | #### #### #### #### (4-4-4-4) ###### ############# (6-13) Pattern not known for 17-18 digit cards. |
In this example, the CreditCardNumberTextWatcher class will be based on the first characters that the user enters in EditText to detect the Credit Card type:

Next, based on the Credit Card type, it formats the card number that the user is entering:

Visa Electron Credit Card Type. (####-####-####-####) (4-4-4-4)

UATP Credit Card Type. (####-#####-######) (4-5-6)
Alright, on Android Studio, create a Project:
- File > New > New Project > Empty Activity
- Name: TextWatcherCreditCardExample
- Package name: org.o7planning.textwatchercreditcardexample
- Language: Java
Copy a few Images into the drawable folder of the project:

CreditCardType.java
package org.o7planning.textwatchercreditcardexample; import java.util.Arrays; public enum CreditCardType { // 4 VISA("Visa", "icon_visa", 13, 19, new int[]{4}, new int[]{4,4,4,4,4} ), // 4026, 417500, 4405, 4508, 4844, 4913, 4917 VISA_ELECTRON("Visa Electron", "icon_visa_electron", 16, 16, new int[]{4026, 417500, 4405, 4508, 4844, 4913, 4917}, new int[]{4,4,4,4} ), // 34, 37 AMERICAN_EXPRESS("American Express", "icon_american_express", 15, 15, new int[]{34, 37}, new int[]{4,6,5} ), // 51‑55, 222100‑272099 MASTERCARD("MasterCard", "icon_mastercard", 16, 16, concat(intRange(51,55), intRange(222100,272099)), new int[]{4,4,4,4} ), // 6011, 622126‑622925, 644‑649, 65 DISCOVER("MasterCard", "icon_mastercard", 16, 16, append(concat(intRange(622126,622925), intRange(644,649)),6011,65), new int[]{4,4,4,4} ), // 3528‑3589 JCB("JCB", "icon_jcb", 16, 16, intRange(3528,3589), new int[]{4,4,4,4} ), // 1 UATP("UATP", "icon_uatp", 15, 15, new int[]{1}, new int[]{4,5,6} ), // 5019 DANKORT("Dankort", "icon_dankort", 16, 16, new int[]{5019}, new int[]{4,4,4,4} ); public static final int[] DEFAULT_BLOCK_LENGTHS = new int[] {4, 4, 4, 4, 4}; public static final int DEFAULT_MAX_LENGTH = 4 * 5 ; private String[] prefixs; private int[] blockLengths; private String name; // Name of Image in "drawable" folder. private String imageResourceName; private int minLength; private int maxLength; CreditCardType(String name, String imageResourceName, int minLength, int maxLength, int[] intPrefixs, int[] blockLengths) { this.name = name; this.imageResourceName = imageResourceName; if(intPrefixs!= null) { this.prefixs = new String[intPrefixs.length]; for(int i=0;i< intPrefixs.length;i++) { this.prefixs[i] = String.valueOf(intPrefixs[i]); } } this.minLength = minLength; this.maxLength = maxLength; this.blockLengths = blockLengths; } public String getName() { return name; } public int getMinLength() { return minLength; } public int getMaxLength() { return maxLength; } public String[] getPrefixs() { return this.prefixs; } public int[] getBlockLengths() { return this.blockLengths; } public String getImageResourceName() { return this.imageResourceName; } private static int[] intRange(int from, int to) { int length = to - from + 1; int[] ret = new int[length]; for(int i= from; i < to + 1; i++) { ret[i-from] = i; } return ret; } private static int[] concat(int[] first, int[] second) { int[] both = Arrays.copyOf(first, first.length + second.length); System.arraycopy(second, 0, both, first.length, second.length); return both; } private static int[] append(int[] first, int ... value) { if(value == null || value.length == 0) { return first; } int[] both = Arrays.copyOf(first, first.length + value.length); for(int i = 0; i < value.length; i++) { both[first.length + i] = value[i]; } return both; } public static CreditCardType detect(String creditCardNumber) { if(creditCardNumber == null || creditCardNumber.isEmpty()) { return null; } CreditCardType found = null; int max = 0; for(CreditCardType type: CreditCardType.values()) { for(String prefix : type.prefixs) { if(creditCardNumber.startsWith(prefix) && prefix.length() > max) { found = type; max = prefix.length(); } } } return found; } }

CreditCardNumberTextWatcher.java
package org.o7planning.textwatchercreditcardexample; import android.graphics.drawable.Drawable; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.widget.EditText; public class CreditCardNumberTextWatcher implements TextWatcher { private static final String LOG_TAG = "AndroidExample"; public static final char SEPARATOR = '-'; private EditText editText; private int after; private String beforeString; public CreditCardNumberTextWatcher(EditText editText) { this.editText = editText; } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { this.after = after; this.beforeString = s.toString(); Log.e(LOG_TAG, "@@beforeTextChanged s=" + s + " . start="+ start+" . after=" + after+" . count="+ count); } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { Log.e(LOG_TAG, "@@onTextChanged s=" + s + " . start="+ start+" . before=" + before+" . count="+ count); String newText = s.toString(); String textPrefix = newText.substring(0, start); String textInserted = newText.substring(start, start + this.after); String textSuffix = newText.substring(start + this.after); String textBeforeCursor = textPrefix + textInserted; // User delete the SEPARATOR. if(this.after == 0 && count == 0 && beforeString.charAt(start) == SEPARATOR) { if(start > 0) { textPrefix = textPrefix.substring(0, textPrefix.length() -1); } } // Non-digit String regex = "[^\\d]"; String textPrefixClean = textPrefix.replaceAll(regex, ""); String textInsertedClean = textInserted.replaceAll(regex, ""); String textSuffixClean = textSuffix.replaceAll(regex, ""); String textBeforeCursorClean = textPrefixClean + textInsertedClean; // creditCardNumber String newTextClean = textPrefixClean + textInsertedClean + textSuffixClean; CreditCardType creditCardType = this.showDetectedCreditCardImage(newTextClean); int[] blockLengths = CreditCardType.DEFAULT_BLOCK_LENGTHS; // {4,4,4,4,4} int minLength = 0; int maxLength = CreditCardType.DEFAULT_MAX_LENGTH; // 4*5 if(creditCardType != null) { blockLengths = creditCardType.getBlockLengths(); minLength = creditCardType.getMinLength(); maxLength = creditCardType.getMaxLength(); } Log.i(LOG_TAG, "newTextClean= " + newTextClean); int[] separatorIndexs = new int[blockLengths.length]; for(int i=0; i < separatorIndexs.length; i++) { if(i==0) { separatorIndexs[i] = blockLengths[i]; } else { separatorIndexs[i] = blockLengths[i] + separatorIndexs[i-1]; } } Log.i(LOG_TAG, "blockLengths= " + this.toString(blockLengths)); Log.i(LOG_TAG, "separatorIndexs= " + this.toString(separatorIndexs)); int cursorPosition = start + this.after - textBeforeCursor.length() + textBeforeCursorClean.length(); StringBuilder sb = new StringBuilder(); int separatorCount = 0; int cursorPositionDelta = 0; int LOOP_MAX = Math.min(newTextClean.length(), maxLength); for(int i = 0; i < LOOP_MAX; i++) { sb.append(newTextClean.charAt(i)); if(this.contains(separatorIndexs,i + 1) && i < LOOP_MAX - 1) { sb.append(SEPARATOR); separatorCount++; if(i < cursorPosition) { cursorPositionDelta++; } } } cursorPosition= cursorPosition + cursorPositionDelta; String textFormatted = sb.toString(); if(cursorPosition > textFormatted.length()) { cursorPosition = textFormatted.length(); } this.editText.removeTextChangedListener(this); this.editText.setText(textFormatted); this.editText.addTextChangedListener(this); this.editText.setSelection(cursorPosition); } @Override public void afterTextChanged(Editable s) { } private String toString(int[] array) { StringBuilder sb= new StringBuilder(); for(int i=0;i< array.length;i++) { if(i == 0) { sb.append("[").append(array[i]); } else { sb.append(", ").append(array[i]); } } sb.append("]"); return sb.toString(); } private boolean contains(int[] values, int value) { for(int i=0;i<values.length;i++) { if(values[i] == value) { return true; } } return false; } private CreditCardType showDetectedCreditCardImage(String creditCardNumber) { CreditCardType type = CreditCardType.detect(creditCardNumber); if(type != null) { Drawable icon = ResourceUtils.getDrawableByName(this.editText.getContext(), type.getImageResourceName()); this.editText.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null); } else { Drawable icon = ResourceUtils.getDrawableByName(this.editText.getContext(), "icon_none"); this.editText.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null); } return type; } }
ResourceUtils.java
package org.o7planning.textwatchercreditcardexample; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; public class ResourceUtils { public static Drawable getDrawableByName(Context context, String resourceName) { Resources resources = context.getResources(); final int resourceId = resources.getIdentifier(resourceName, "drawable", context.getPackageName()); return resources.getDrawable(resourceId); } }
Here is the interface of the example:

activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/textView81" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:layout_marginRight="16dp" android:text="Enter Your Credit Card Number:" android:textSize="20sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/editText_creditCardNumber" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:layout_marginEnd="16dp" android:layout_marginRight="16dp" android:drawableRight="@drawable/icon_none" android:ems="10" android:inputType="phone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView81" /> </androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package org.o7planning.textwatchercreditcardexample; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.text.TextWatcher; import android.widget.EditText; public class MainActivity extends AppCompatActivity { private EditText editTextCreditCardNumber; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.editTextCreditCardNumber = (EditText) this.findViewById(R.id.editText_creditCardNumber); TextWatcher textWatcher = new CreditCardNumberTextWatcher(this.editTextCreditCardNumber); this.editTextCreditCardNumber.addTextChangedListener(textWatcher); } }