openplanning

Định dạng số thẻ tín dụng với Android TextWatcher

  1. Credit Card Number Pattern
  2. Example: TextWatcher Credit Card Number

1. Credit Card Number Pattern

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.

2. Example: TextWatcher Credit Card Number

Trong ví dụ này, Lớp CreditCardNumberTextWatcher sẽ dựa trên các ký tự đầu tiên mà người dùng nhập vào EditText để phát hiện ra kiểu Credit Card:
Tiếp đó nó dựa trên kiểu Credit Card để định dạng số thẻ mà người dùng đang nhập vào:
Visa Electron Credit Card Type. (####-####-####-####) (4-4-4-4)
UATP Credit Card Type. (####-#####-######) (4-5-6)
OK, Trên Android Studio tạo mới một Project:
  • File > New > New Project > Empty Activity
    • Name: TextWatcherCreditCardExample
    • Package name: org.o7planning.textwatchercreditcardexample
    • Language: Java
Copy một vài file ảnh vào thư mục drawable của 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);
    }
}
Giao diện của ví dụ:
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);
    }
}

Các hướng dẫn lập trình Android

Show More