以mm / yy格式格式化到期日期

时间:2013-12-16 09:54:41

标签: android android-edittext textwatcher

您好我正在编写一个edittext,其中我想要MM / YY格式的信用卡有效期。 我想实现的算法如下: 如果用户输入2到9之间的任何内容。我将文本输入更改为02 /到09 / 如果用户输入1,那么我等待下一个数字并检查int值月份是否小于12。 这是我的代码。

@Override
            public void afterTextChanged(Editable s) { 
            String input = s.toString();
                if (s.length() == 1) {
                        int month = Integer.parseInt(input);
                        if (month > 1) {
                            mExpiryDate.setText("0" + mExpiryDate.getText().toString() + "/");
                            mExpiryDate.setSelection(mExpiryDate.getText().toString().length());
                            mSeperator = true;
                        }

                }
                else if (s.length() == 2) {
                        int month = Integer.parseInt(input);
                        if (month <= 12) {
                            mExpiryDate.setText(mExpiryDate.getText().toString() + "/");
                            mExpiryDate.setSelection(mExpiryDate.getText().toString().length());                            
                            mSeperator = true;
                        }
                }
                else {

                }

            }

这一功能正常,直到我按下软键返回按钮。反斜杠永远不会回头。 原因是第二个if条件总是满足。 我很困惑如何解决这个问题。如何处理后面的后退按钮?请帮忙。

6 个答案:

答案 0 :(得分:11)

请参阅上面的评论以了解您的问题。您可以使用它来验证textwatcher的用户输入:

SimpleDateFormat formatter = 
    new SimpleDateFormat("MM/yy", Locale.GERMANY);
Calendar expiryDateDate = Calendar.getInstance();
try {
    expiryDateDate.setTime(formatter.parse(mExpiryDate.getText().toString()));
} catch (ParseException e) {
    //not valid
}
// expiryDateDate has a valid date from the user

所以完成它将是:

String lastInput ="";

@Override
public void afterTextChanged(Editable s) { 
     String input = s.toString();
     SimpleDateFormat formatter = new SimpleDateFormat("MM/yy", Locale.GERMANY);
    Calendar expiryDateDate = Calendar.getInstance();
    try {
        expiryDateDate.setTime(formatter.parse(input));
    } catch (ParseException e) {
        if (s.length() == 2 && !lastInput.endsWith("/")) {
            int month = Integer.parseInt(input);
            if (month <= 12) {
               mExpiryDate.setText(mExpiryDate.getText().toString() + "/");
            }
        }else if (s.length() == 2 && lastInput.endsWith("/")) {
            int month = Integer.parseInt(input);
            if (month <= 12) {
               mExpiryDate.setText(mExpiryDate.getText().toString().subStr(0,1);
            }
        }
        lastInput = mExpiryDate.getText().toString();
        //because not valid so code exits here
        return;
    }
    // expiryDateDate has a valid date from the user
    // Do something with expiryDateDate here
}

最后完整的解决方案:

String input = s.toString();
SimpleDateFormat formatter = new SimpleDateFormat("MM/yy", Locale.GERMANY);
Calendar expiryDateDate = Calendar.getInstance();
try {
   expiryDateDate.setTime(formatter.parse(input));
} catch (ParseException e) {

} catch (java.text.ParseException e) {
if (s.length() == 2 && !mLastInput.endsWith("/")) {
   int month = Integer.parseInt(input);
   if (month <= 12) {
      mExpiryDate.setText(mExpiryDate.getText().toString() + "/");
      mExpiryDate.setSelection(mExpiryDate.getText().toString().length());
   }
}else if (s.length() == 2 && mLastInput.endsWith("/")) {
   int month = Integer.parseInt(input);
    if (month <= 12) {
       mExpiryDate.setText(mExpiryDate.getText().toString().substring(0,1));
       mExpiryDate.setSelection(mExpiryDate.getText().toString().length());
    } else {
       mExpiryDate.setText("");
       mExpiryDate.setSelection(mExpiryDate.getText().toString().length());
       Toast.makeText(getApplicationContext(), "Enter a valid month", Toast.LENGTH_LONG).show();
    }
} else if (s.length() == 1){
    int month = Integer.parseInt(input);
    if (month > 1) {
       mExpiryDate.setText("0" + mExpiryDate.getText().toString() + "/");
       mExpiryDate.setSelection(mExpiryDate.getText().toString().length());
    }
}
else {

}
mLastInput = mExpiryDate.getText().toString();
return;

答案 1 :(得分:4)

上面的@alex解决方案很好,但在几个实例中失败了。就像你试图删除斜杠一样,因为当你试图删除斜杠时,它永远不会真正达到(s.length(=)=&amp;&amp; mLastInput.endsWith(“/”))当它(如果( s.length()== 2&amp;&amp;!mLastInput.endsWith(“/”)因此它会给出斜杠不会删除的错觉。

如果用户完成日期,即08/16,然后将光标恢复到月份并删除,它也会失败,如果日期可能有些结果如0/1那么它也会失败。所以我刚刚对@ alex的解决方案进行了一些更改。

//Make sure for mExpiryDate to be accepting Numbers only
boolean isSlash = false; //class level initialization 
private void formatCardExpiringDate(Editable s){
    String input = s.toString();
    String mLastInput = "";

    SimpleDateFormat formatter = new SimpleDateFormat("MM/yy",     Locale.ENGLISH);
    Calendar expiryDateDate = Calendar.getInstance();

    try {
        expiryDateDate.setTime(formatter.parse(input));
    } catch (java.text.ParseException e) {
        if (s.length() == 2 && !mLastInput.endsWith("/") && isSlash) {
            isSlash = false;
            int month = Integer.parseInt(input);
            if (month <= 12) {
                     mExpiryDate.setText(mExpiryDate.getText().toString().substring(0, 1));
                mExpiryDate.setSelection(mExpiryDate.getText().toString().length());
            } else {
                s.clear();
                mExpiryDate.setText("");
                mExpiryDate.setSelection(mExpiryDate.getText().toString().length());
                Toast.makeText(context.getApplicationContext(), "Enter a valid month", Toast.LENGTH_LONG).show();
            }
        }else if (s.length() == 2 && !mLastInput.endsWith("/") && !isSlash) {
            isSlash = true;
            int month = Integer.parseInt(input);
            if (month <= 12) {
                mExpiryDate.setText(mExpiryDate.getText().toString() + "/");
                mExpiryDate.setSelection(mExpiryDate.getText().toString().length());
            }else if(month > 12){
                edCardDate.setText("");
                mExpiryDate.setSelection(mExpiryDate.getText().toString().length());
                s.clear();
                _toastMessage("invalid month", context);
            }


        } else if (s.length() == 1) {

            int month = Integer.parseInt(input);
            if (month > 1 && month < 12) {
                isSlash = true;
                mExpiryDate.setText("0" + mExpiryDate.getText().toString() + "/");
                mExpiryDate.setSelection(mExpiryDate.getText().toString().length());
            }
        }

        mLastInput = mExpiryDate.getText().toString();
        return;
    }
}
//wrap method formatCardExpiringDate around try catch or wrap the entire code in try catch, catching NumberFormateException. To take care of situations when s.length() == 2 and there is a a number in from of the slash
@Override
public void afterTextChanged(Editable s) { 
   try{
     formatCardExpiringDate(s)
    }catch(NumberFormatException e){
      s.clear(); 
      //Toast message here.. Wrong date formate

    }
}

当用户点击“提交”时,我会再次检查“月”,以便更加确定。

String expdate[] = mExpiryDate.getText().toString().split("/");
if(Integer.ParseInt(expDate[0]) > 12){
  // Toast message "wrong date format".... 
}

我希望这有帮助......

答案 2 :(得分:1)

愿你能做到这样:

boolean validateCardExpiryDate(String expiryDate) {
    return expiryDate.matches("(?:0[1-9]|1[0-2])/[0-9]{2}");
}

翻译为:

非捕获组(非捕获组?):0后跟1-9,或1后跟0-2 其次是 ”/” 然后是0-9,两次。 ...所以这个版本需要零填充月(01 - 12)。加一个?在第0个之后防止这种情况。

希望它会帮助你...... !!!

答案 3 :(得分:1)

TextWatchers用于在每次编辑时更新外部属性(例如,在ViewModel中)。

TextWatchers不应用于修改EditText自己的文字。

对于格式输入,您应使用InputFilter而不是TextWatcher。 请尝试以下方法:

在项目中包含以下类:

/**
 * InputFilter to ensure user enters valid expiry date in a credit card.
 * User is only allowed to type from beginning-to-end without copy-pasting or inserting characters in the middle.
 * The user may enter any month 01 -> 12.
 * The user can enter, at minimum, the current year or any year that follows.
 *
 * Note: `inputType` of the EditText should be `number` and `digits` should be `0123456789/`.
 *
 * Created by W.K.S on 30/07/2017 (Licensed under GNU Public License - original author must be credited)
 */

public class CreditCardExpiryInputFilter implements InputFilter {

    private final String currentYearLastTwoDigits;

    public CreditCardExpiryInputFilter() {
        currentYearLastTwoDigits = new SimpleDateFormat("yy", Locale.US).format(new Date());
    }

    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        //do not insert if length is already 5
        if (dest != null & dest.toString().length() == 5) return "";
        //do not insert more than 1 character at a time
        if (source.length() > 1) return "";
        //only allow character to be inserted at the end of the current text
        if (dest.length() > 0 && dstart != dest.length()) return "";

        //if backspace, skip
        if (source.length() == 0) {
            return source;
        }

        //At this point, `source` is a single character being inserted at `dstart`. 
        //`dstart` is at the end of the current text.

        final char inputChar = source.charAt(0);

        if (dstart == 0) {
            //first month digit
            if (inputChar > '1') return "";
        }
        if (dstart == 1) {
            //second month digit
            final char firstMonthChar = dest.charAt(0);
            if (firstMonthChar == '0' && inputChar == '0') return "";
            if (firstMonthChar == '1' && inputChar > '2') return "";

        }
        if (dstart == 2) {
            final char currYearFirstChar = currentYearLastTwoDigits.charAt(0);
            if (inputChar < currYearFirstChar) return "";
            return "/".concat(source.toString());
        }
        if (dstart == 4){
            final String inputYear = ""+dest.charAt(dest.length()-1)+source.toString();
            if (inputYear.compareTo(currentYearLastTwoDigits) < 0) return "";
        }

        return source;
    }
}

CreditCardExpiryInputFilter应用到EditText

EditText expiryEditText = findViewById(this, R.id.edittext_expiry_date);
expiryEditText.setFilters(new InputFilter[]{new CreditCardExpiryInputFilter()});

在xml中,将inputType设置为number,将digits设置为0123456789/

<EditText
    android:id="@+id/edittext_expiry_date"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="number"
    android:digits="0123456789/"
    />

答案 4 :(得分:0)

我使用了Uche Dim的解决方案,并修复了一些问题并清理了代码。

所以我代码中的关键改进是:

  1. 如果用户尝试输入“ 13”,则只会输入“ 1”。
  2. 当用户开始输入年份时,在删除斜杠后,将输入一个斜杠,以便保持MM / yy格式。

所有这些几乎就像是Play商店的新卡到期字段。

我创建了一个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)
    private var mLastInput = ""

    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)
            5 -> validateDateAndCallListener(input)
        }
        mLastInput = mTextInputLayout.editText!!.text.toString()
    }

    private fun validateDateAndCallListener(input: String) {
        try {
            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 != '/') {
            val month = input.substring(0, 2)
            mTextInputLayout.editText!!.setText("$month/$lastCharacter")
            mTextInputLayout.editText!!.setSelection(mTextInputLayout.editText!!.text.toString().length)
        }
    }

    @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
    }
})

用法(Java):

CardExpiryTextWatcher.attachTo(inputCardExpiry, mServerDate, new CardExpiryTextWatcher.DateListener() {
    @Override
    public void onExpiryEntered(@NonNull Date date) {
        // TODO implement your handling
    }
});

注意:inputCardExpiry是包含编辑文本的InputTextLayout

答案 5 :(得分:0)

将TextWatcher添加到您的EditText中,并使用REGEX进行验证。

TextWatcher:

GUI

使用REGEX进行验证:

etCardExpiry.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                if (editable.length() > 0 && (editable.length() % 3) == 0) {
                    final char c = editable.charAt(editable.length() - 1);
                    if ('/' == c) {
                        editable.delete(editable.length() - 1, editable.length());
                    }
                }
                if (editable.length() > 0 && (editable.length() % 3) == 0) {
                    char c = editable.charAt(editable.length() - 1);
                    if (Character.isDigit(c) && TextUtils.split(editable.toString(), String.valueOf("/")).length <= 2) {
                        editable.insert(editable.length() - 1, String.valueOf("/"));
                    }
                }
            }
        });