Skip to content

Instantly share code, notes, and snippets.

@orcchg
Created April 19, 2018 17:29
Show Gist options
  • Save orcchg/b1656ace61b78b51dd93a4a3ef0d24a0 to your computer and use it in GitHub Desktop.
Save orcchg/b1656ace61b78b51dd93a4a3ef0d24a0 to your computer and use it in GitHub Desktop.
TextWatcher for expiry date MM/YY automatically adding slash. For Android
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.NonNull;
import android.text.Editable;
import android.text.Spannable;
import android.text.TextWatcher;
import android.text.style.ReplacementSpan;
public class ExpiryDateTextWatcher implements TextWatcher {
private int maxLength = 5;
private boolean internalStopFormatFlag;
public ExpiryDateTextWatcher() {}
@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) {
if (internalStopFormatFlag) {
return;
}
internalStopFormatFlag = true;
formatExpiryDate(s, maxLength);
internalStopFormatFlag = false;
}
public static void formatExpiryDate(@NonNull Editable expiryDate, int maxLength) {
int textLength = expiryDate.length();
// first remove any previous span
SlashSpan[] spans = expiryDate.getSpans(0, expiryDate.length(), SlashSpan.class);
for (int i = 0; i < spans.length; ++i) {
expiryDate.removeSpan(spans[i]);
}
// then truncate to max length
if (maxLength > 0 && textLength > maxLength - 1) {
expiryDate.replace(maxLength, textLength, "");
--textLength;
}
// finally add margin spans
for (int i = 1; i <= ((textLength - 1) / 2); ++i) {
int end = i * 2 + 1;
int start = end - 1;
SlashSpan marginSPan = new SlashSpan();
expiryDate.setSpan(marginSPan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
public static class SlashSpan extends ReplacementSpan {
public SlashSpan() {}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
float[] widths = new float[end - start];
float[] slashWidth = new float[1];
paint.getTextWidths(text, start, end, widths);
paint.getTextWidths("/", slashWidth);
int sum = (int) slashWidth[0];
for (int i = 0; i < widths.length; ++i) {
sum += widths[i];
}
return sum;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
String xtext = "/" + text.subSequence(start, end);
canvas.drawText(xtext, 0, xtext.length(), x, y, paint);
}
}
}
@sumanpula
Copy link

sumanpula commented Aug 22, 2019

@milaptank Hello Please user below class to format date,

import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import java.util.Calendar;
import java.util.Date;

public class DateMask implements TextWatcher {
private final static String TAG = DateMask.class.getSimpleName();
private String currentString = "";
private String separator = "/";
private DateMaskingCallback dateMaskingCallback;
private static final String INVALID_DATE_MSG = "Enter valid Date";
private static final String INVALID_MONTH_MSG = "Enter valid Month";

public DateMask(DateMaskingCallback dateMaskingCallback, String separator) {
    this.dateMaskingCallback = dateMaskingCallback;
    this.separator = separator;
}

@Override
public void beforeTextChanged(CharSequence charSequence, int start, int before, int count) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
    try {
        String dateString = s.toString();
        Log.e(TAG, "date current string "+currentString);
        if (!currentString.equalsIgnoreCase(dateString)) {
            currentString = dateString;
            String errorString = "";
            if (dateString.length() == 2 && before == 0) {
                if (dateString.equalsIgnoreCase("00")) {
                    errorString = INVALID_MONTH_MSG;
                } else if (Integer.parseInt(dateString) < 1 || Integer.parseInt(dateString) > 12) {
                    errorString = INVALID_MONTH_MSG;
                }
            } else if (dateString.length() == 3 && before == 0) {
                String day = dateString.substring(2, dateString.length()-1);
                dateString = dateString.substring(0,2)+separator+day;
            } else if (dateString.length() == 4 && before == 0) {
                String month = dateString.substring(3, 4);
                if (Integer.parseInt(month) > 12) {
                    errorString = INVALID_DATE_MSG;
                }
            } else if (dateString.length() == 6 && before == 0) {
                String yearString = dateString.substring(3,5);
                dateString = dateString.substring(0, 3)+ yearString+separator;
                if (yearString.startsWith("0")) {
                    errorString = "Please start year with > 0";
                }
            } else if (dateString.length() >= 7 && before == 0) {
                Date currentDate = Calendar.getInstance().getTime();
                Date enteredDate = new Date(dateString);
                if (dateString.length() >= 10 && currentDate.getTime() < enteredDate.getTime()) {
                    errorString = "Future dates are not valid in dob";
                }
            }
            Log.e(TAG, "dateString "+dateString +" error String "+errorString);
            if (errorString.isEmpty()) {
               dateMaskingCallback.dateOfBirthValidation(true, dateString, errorString);
            } else {
                dateMaskingCallback.dateOfBirthValidation(false, dateString, errorString);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Override
public void afterTextChanged(Editable editable) {

}

public interface DateMaskingCallback {
    void dateOfBirthValidation(final boolean isValid, final String dateOfBirth, final String error) throws Exception;
}

}

// Usage is like below
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    
    final EditText dob = findViewById(R.id.etDob);
    dob.addTextChangedListener(new DateMask(new DateMask.DateMaskingCallback() {
        @Override
        public void dateOfBirthValidation(boolean isValid, String dateOfBirth, String error) throws Exception {
            if (isValid) {
                // sets formatted string here
                dob.setText(dateOfBirth);
                dob.setSelection(dateOfBirth.length());
            } else {
                // sets error here
                dob.setError(error);
            }
        [}](url)
    }, "/"));
}

Screenshot_1566466140

@milaptank
Copy link

@sumanpula thank you so much I will implement and let you know

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment