Я использовал решение Uche Dim, исправил некоторые проблемы и почистил код.
Итак, ключевые улучшения в моем коде:
- если пользователь попытается ввести «13», будет введено только «1».
- когда пользователь начинает вводить год, после того, как он удалит косую черту, она будет добавлена, чтобы сохранить формат ММ/гг.
В целом это почти как поле истечения срока действия новых карточек в Play Store.
Я создал класс Kotlin, но использование также добавлено для Java.
Класс CardExpiryTextWatcher:
class CardExpiryTextWatcher(private val mTextInputLayout: TextInputLayout,
private val mServerDate: Date,
private val mListener: DateListener) : TextWatcher {
private val mExpiryDateFormat = SimpleDateFormat("MM/yy", Locale.US).apply {
isLenient = false
}
private var mLastInput = ""
private var mIgnoreAutoValidationOnce = false
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
@SuppressLint("SetTextI18n")
override fun afterTextChanged(s: Editable) {
val input = s.toString()
when (s.length) {
1 -> handleMonthInputForFirstCharacter(input)
2 -> handleMonthInputForSecondCharacter(input)
3 -> addSlashIfNotAddedAtEnd(input)
4 -> addSlashIfNotAddedInMiddle(input)
5 -> validateDateAndCallListener(input)
}
mLastInput = mTextInputLayout.editText!!.text.toString()
}
private fun validateDateAndCallListener(input: String) {
try {
if (mIgnoreAutoValidationOnce) {
mIgnoreAutoValidationOnce = false
return
}
if (input[2] == '/') {
val date = mExpiryDateFormat.parse(input)
validateCardIsNotExpired(date)
}
} catch (e: ParseException) {
mTextInputLayout.error = mTextInputLayout.context.getString(R.string.card_exp_date_error)
}
}
private fun validateCardIsNotExpired(cardExpiry: Date) {
if (DateUtils.isDateBefore(cardExpiry, mServerDate)) {
mTextInputLayout.error = mTextInputLayout.context.getString(R.string.card_expired)
return
}
mListener.onExpiryEntered(cardExpiry)
}
@SuppressLint("SetTextI18n")
private fun addSlashIfNotAddedAtEnd(input: String) {
val lastCharacter = input[input.length - 1]
if (lastCharacter != '/' && !input.startsWith('/')) {
val month = input.substring(0, 2)
mTextInputLayout.editText!!.setText("$month/$lastCharacter")
mTextInputLayout.editText!!.setSelection(mTextInputLayout.editText!!.text.toString().length)
}
}
@SuppressLint("SetTextI18n")
private fun addSlashIfNotAddedInMiddle(input: String) {
if (input.contains('/')) {
return
}
val month = input.substring(0, 2)
val year = input.substring(2, 4)
mIgnoreAutoValidationOnce = true
mTextInputLayout.editText!!.setText("$month/$year")
mTextInputLayout.editText!!.setSelection(2)
}
@SuppressLint("SetTextI18n")
private fun handleMonthInputForSecondCharacter(input: String) {
if (mLastInput.endsWith("/")) {
return
}
val month = Integer.parseInt(input)
if (month > 12) {
mTextInputLayout.editText!!.setText(mLastInput)
mTextInputLayout.editText!!.setSelection(mTextInputLayout.editText!!.text.toString().length)
mTextInputLayout.error = mTextInputLayout.context.getString(R.string.card_exp_date_error)
} else {
mTextInputLayout.editText!!.setText("${mTextInputLayout.editText!!.text}/")
mTextInputLayout.editText!!.setSelection(mTextInputLayout.editText!!.text.toString().length)
}
}
@SuppressLint("SetTextI18n")
private fun handleMonthInputForFirstCharacter(input: String) {
val month = Integer.parseInt(input)
if (month in 2..11) {
mTextInputLayout.editText!!.setText("0${mTextInputLayout.editText!!.text}/")
mTextInputLayout.editText!!.setSelection(mTextInputLayout.editText!!.text.toString().length)
}
}
interface DateListener {
fun onExpiryEntered(date: Date)
}
companion object {
@JvmStatic
fun attachTo(textInputLayout: TextInputLayout, serverDate: Date, listener: DateListener) {
textInputLayout.editText!!.addTextChangedListener(
CardExpiryTextWatcher(textInputLayout, serverDate, listener))
}
}
}
Использование (Котлин):
CardExpiryTextWatcher.attachTo(inputCardExpiry, mServerDate, object : CardExpiryTextWatcher.DateListener {
override fun onExpiryEntered(date: Date) {
// TODO implement your handling
}
})
Использование (Ява):
CardExpiryTextWatcher.attachTo(inputCardExpiry, mServerDate, new CardExpiryTextWatcher.DateListener() {
@Override
public void onExpiryEntered(@NonNull Date date) {
// TODO implement your handling
}
});
Предупреждение. Дата всегда будет состоять из двух цифр (например, 4 декабря будет 12 апреля, а не 12 апреля), но если пользователь удалит одну цифру из даты, она может стать 12 апреля, поэтому вам нужно запустить следующий метод перед проверкой:
/**
* Makes sure that the date's day is of 2 digits, (e.g. 4/12 will be converted to 04/12)
* */
fun normalizeExpiryDate(expiryDate: String): String {
if (expiryDate.length == 4 && expiryDate.indexOf('/') == 1) {
return "0$expiryDate"
}
return expiryDate
}
Примечание. inputCardExpiry
— это InputTextLayout
, который содержит EditText.
person
Sufian
schedule
26.06.2019
SimpleDateFormat
? Это сделано для рабочих/разборных дат. - person alex   schedule 16.12.2013