Как я могу отформатировать EditText
так, чтобы он соответствовал формату «dd/mm/yyyy
» так же, как мы можем отформатировать с помощью TextWatcher
, чтобы маскировать пользовательский ввод, чтобы он выглядел как «0,05€». Я не говорю об ограничении символов или проверке даты, просто маскируя предыдущий формат.
Как замаскировать EditText для отображения формата даты дд/мм/гггг
Ответы (10)
Я написал это TextWatcher
для проекта, надеюсь, это будет кому-то полезно. Обратите внимание, что он не проверяет дату, введенную пользователем, и вы должны обработать это при изменении фокуса, поскольку пользователь может не закончить ввод даты.
Обновление от 25 июня. Сделано вики, чтобы увидеть, достигнем ли мы лучшего финального кода.
Обновление от 06 07 Я наконец-то добавил проверку самому наблюдателю. Он сделает следующее с недопустимыми датами:
- Если месяц больше 12, это будет 12 (декабрь)
- Если дата больше даты выбранного месяца, сделайте ее максимальной для этого месяца.
- Если год не находится в диапазоне
1900-2100
, измените его так, чтобы он находился в диапазоне
Эта проверка соответствует моим потребностям, но некоторые из вас могут захотеть немного ее изменить, диапазоны легко изменяются, и вы можете подключить эту проверку, например, к сообщению Toast
, чтобы уведомить пользователя о том, что мы изменили его/ее дату с момента ее был недействителен.
В этом коде я буду предполагать, что у нас есть ссылка на наш EditText
с именем date
, к которому прикреплен этот TextWatcher
, это можно сделать примерно так:
EditText date;
date = (EditText)findViewById(R.id.whichdate);
date.addTextChangedListener(tw);
TextWatcher tw = new TextWatcher() {
private String current = "";
private String ddmmyyyy = "DDMMYYYY";
private Calendar cal = Calendar.getInstance();
Когда пользователь изменяет текст EditText
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!s.toString().equals(current)) {
String clean = s.toString().replaceAll("[^\\d.]|\\.", "");
String cleanC = current.replaceAll("[^\\d.]|\\.", "");
int cl = clean.length();
int sel = cl;
for (int i = 2; i <= cl && i < 6; i += 2) {
sel++;
}
//Fix for pressing delete next to a forward slash
if (clean.equals(cleanC)) sel--;
if (clean.length() < 8){
clean = clean + ddmmyyyy.substring(clean.length());
}else{
//This part makes sure that when we finish entering numbers
//the date is correct, fixing it otherwise
int day = Integer.parseInt(clean.substring(0,2));
int mon = Integer.parseInt(clean.substring(2,4));
int year = Integer.parseInt(clean.substring(4,8));
mon = mon < 1 ? 1 : mon > 12 ? 12 : mon;
cal.set(Calendar.MONTH, mon-1);
year = (year<1900)?1900:(year>2100)?2100:year;
cal.set(Calendar.YEAR, year);
// ^ first set year for the line below to work correctly
//with leap years - otherwise, date e.g. 29/02/2012
//would be automatically corrected to 28/02/2012
day = (day > cal.getActualMaximum(Calendar.DATE))? cal.getActualMaximum(Calendar.DATE):day;
clean = String.format("%02d%02d%02d",day, mon, year);
}
clean = String.format("%s/%s/%s", clean.substring(0, 2),
clean.substring(2, 4),
clean.substring(4, 8));
sel = sel < 0 ? 0 : sel;
current = clean;
date.setText(current);
date.setSelection(sel < current.length() ? sel : current.length());
}
}
Мы также реализуем две другие функции, потому что мы должны
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void afterTextChanged(Editable s) {}
};
Это приводит к следующему эффекту, когда удаление или вставка символов показывает или скрывает маску dd/mm/yyyy
. Его должно быть легко изменить, чтобы он соответствовал маскам других форматов, поскольку я старался сделать код как можно более простым.
cal
следующим образом: private Calendar cal = Calendar.getInstance();
иначе я получил бы NullPointerException
в части cal.set(Calendar.MONTH, mon-1);
- person Thomas; 22.01.2015
current
, ddmmyyyy
и т. д. вы сделали это вики, пожалуйста, уточните свой код
- person null; 31.10.2017
23/11/2016
я попытаюсь удалить из строки только 11
, то получу частичную дату 23/20/16YY
.
- person QED; 22.02.2018
day = (day > cal.getActualMaximum(Calendar.DATE))? cal.getActualMaximum(Calendar.DATE):day;
следующим: int MIN_DATE = cal.getActualMinimum(Calendar.DATE); int MAX_DATE = cal.getActualMaximum(Calendar.DATE); day = (day < MIN_DATE) ? MIN_DATE : (day > MAX_DATE) ? MAX_DATE : day;
- person Sha2nk; 26.12.2019
Текущий ответ очень хорош и помог мне найти собственное решение. Есть несколько причин, по которым я решил опубликовать собственное решение, хотя на этот вопрос уже есть правильный ответ:
- Я работаю на Котлине, а не на Java. Люди, которые столкнутся с такой же проблемой, должны будут перевести текущее решение.
- Я хотел написать ответ, который был бы более разборчивым, чтобы людям было легче адаптировать его к своим проблемам.
- Как предложил dengue8830, я инкапсулировал решение этой проблемы в класс, чтобы любой мог использовать его, даже не беспокоясь о реализации.
Чтобы использовать его, просто сделайте что-то вроде:
- DateInputMask(mEditText).listen()
И решение показано ниже:
class DateInputMask(val input : EditText) {
fun listen() {
input.addTextChangedListener(mDateEntryWatcher)
}
private val mDateEntryWatcher = object : TextWatcher {
var edited = false
val dividerCharacter = "/"
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (edited) {
edited = false
return
}
var working = getEditText()
working = manageDateDivider(working, 2, start, before)
working = manageDateDivider(working, 5, start, before)
edited = true
input.setText(working)
input.setSelection(input.text.length)
}
private fun manageDateDivider(working: String, position : Int, start: Int, before: Int) : String{
if (working.length == position) {
return if (before <= position && start < position)
working + dividerCharacter
else
working.dropLast(1)
}
return working
}
private fun getEditText() : String {
return if (input.text.length >= 10)
input.text.toString().substring(0,10)
else
input.text.toString()
}
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
}
}
более чистый способ использования кода Хуана Кортеса - поместить его в класс:
public class DateInputMask implements TextWatcher {
private String current = "";
private String ddmmyyyy = "DDMMYYYY";
private Calendar cal = Calendar.getInstance();
private EditText input;
public DateInputMask(EditText input) {
this.input = input;
this.input.addTextChangedListener(this);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.toString().equals(current)) {
return;
}
String clean = s.toString().replaceAll("[^\\d.]|\\.", "");
String cleanC = current.replaceAll("[^\\d.]|\\.", "");
int cl = clean.length();
int sel = cl;
for (int i = 2; i <= cl && i < 6; i += 2) {
sel++;
}
//Fix for pressing delete next to a forward slash
if (clean.equals(cleanC)) sel--;
if (clean.length() < 8){
clean = clean + ddmmyyyy.substring(clean.length());
}else{
//This part makes sure that when we finish entering numbers
//the date is correct, fixing it otherwise
int day = Integer.parseInt(clean.substring(0,2));
int mon = Integer.parseInt(clean.substring(2,4));
int year = Integer.parseInt(clean.substring(4,8));
mon = mon < 1 ? 1 : mon > 12 ? 12 : mon;
cal.set(Calendar.MONTH, mon-1);
year = (year<1900)?1900:(year>2100)?2100:year;
cal.set(Calendar.YEAR, year);
// ^ first set year for the line below to work correctly
//with leap years - otherwise, date e.g. 29/02/2012
//would be automatically corrected to 28/02/2012
day = (day > cal.getActualMaximum(Calendar.DATE))? cal.getActualMaximum(Calendar.DATE):day;
clean = String.format("%02d%02d%02d",day, mon, year);
}
clean = String.format("%s/%s/%s", clean.substring(0, 2),
clean.substring(2, 4),
clean.substring(4, 8));
sel = sel < 0 ? 0 : sel;
current = clean;
input.setText(current);
input.setSelection(sel < current.length() ? sel : current.length());
}
@Override
public void afterTextChanged(Editable s) {
}
}
тогда вы можете использовать его повторно
new DateInputMask(myEditTextInstance);
Попробуйте использовать библиотеку, которая решает эту проблему, так как маскирование недоступно из коробки. Есть много угловых случаев (например, добавление/удаление символов в середине уже замаскированного текста), и для правильной обработки этого вы получите много кода (и ошибок).
Вот некоторые доступные библиотеки:
https://github.com/egslava/edittext-mask
https://github.com/dimitar-zabaznoski/MaskedEditText
https://github.com/pinball83/Masked-Edittext
https://github.com/RedMadRobot/input-mask-android
https://github.com/santalu/mask-edittext
** Имейте в виду, что на момент написания этих библиотек были проблемы, поэтому вы должны выбрать, какая из них подходит вам лучше всего, и протестировать код.
Вики Хуана Кортеса работает как шарм https://stackoverflow.com/a/16889503/3480740
Вот моя версия Kotlin
fun setBirthdayEditText() {
birthdayEditText.addTextChangedListener(object : TextWatcher {
private var current = ""
private val ddmmyyyy = "DDMMYYYY"
private val cal = Calendar.getInstance()
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
if (p0.toString() != current) {
var clean = p0.toString().replace("[^\\d.]|\\.".toRegex(), "")
val cleanC = current.replace("[^\\d.]|\\.", "")
val cl = clean.length
var sel = cl
var i = 2
while (i <= cl && i < 6) {
sel++
i += 2
}
//Fix for pressing delete next to a forward slash
if (clean == cleanC) sel--
if (clean.length < 8) {
clean = clean + ddmmyyyy.substring(clean.length)
} else {
//This part makes sure that when we finish entering numbers
//the date is correct, fixing it otherwise
var day = Integer.parseInt(clean.substring(0, 2))
var mon = Integer.parseInt(clean.substring(2, 4))
var year = Integer.parseInt(clean.substring(4, 8))
mon = if (mon < 1) 1 else if (mon > 12) 12 else mon
cal.set(Calendar.MONTH, mon - 1)
year = if (year < 1900) 1900 else if (year > 2100) 2100 else year
cal.set(Calendar.YEAR, year)
// ^ first set year for the line below to work correctly
//with leap years - otherwise, date e.g. 29/02/2012
//would be automatically corrected to 28/02/2012
day = if (day > cal.getActualMaximum(Calendar.DATE)) cal.getActualMaximum(Calendar.DATE) else day
clean = String.format("%02d%02d%02d", day, mon, year)
}
clean = String.format("%s/%s/%s", clean.substring(0, 2),
clean.substring(2, 4),
clean.substring(4, 8))
sel = if (sel < 0) 0 else sel
current = clean
birthdayEditText.setText(current)
birthdayEditText.setSelection(if (sel < current.count()) sel else current.count())
}
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun afterTextChanged(p0: Editable) {
}
})
}
val cleanC = current.replace("[^\\d.]|\\.", "")
должно быть val cleanC = current.replace("[^\\d.]|\\.".toRegex(), "")
- person Estevex; 12.08.2020
Версия Kotlin без проверки
editText.addTextChangedListener(object : TextWatcher{
var sb : StringBuilder = StringBuilder("")
var _ignore = false
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if(_ignore){
_ignore = false
return
}
sb.clear()
sb.append(if(s!!.length > 10){ s.subSequence(0,10) }else{ s })
if(sb.lastIndex == 2){
if(sb[2] != '/'){
sb.insert(2,"/")
}
} else if(sb.lastIndex == 5){
if(sb[5] != '/'){
sb.insert(5,"/")
}
}
_ignore = true
editText.setText(sb.toString())
editText.setSelection(sb.length)
}
})
добавить android:inputType="date"
к EditText
Этот ответ не применяет полную маску для оставшихся нетипизированных цифр. Тем не менее, это связано и является решением, в котором я нуждался. Он работает аналогично тому, как работает PhoneNumberFormattingTextWatcher
.
По мере ввода добавляется косая черта для разделения даты в формате mm/dd/yyyy
. Он не выполняет никакой проверки — только форматирование.
Нет необходимости в EditText
ссылке. Просто установите слушателя, и он работает. myEditText.addTextChangedListener(new DateTextWatcher());
import android.text.Editable;
import android.text.TextWatcher;
import java.util.Locale;
/**
* Adds slashes to a date so that it matches mm/dd/yyyy.
*
* Created by Mark Miller on 12/4/17.
*/
public class DateTextWatcher implements TextWatcher {
public static final int MAX_FORMAT_LENGTH = 8;
public static final int MIN_FORMAT_LENGTH = 3;
private String updatedText;
private boolean editing;
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int before, int count) {
}
@Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
if (text.toString().equals(updatedText) || editing) return;
String digitsOnly = text.toString().replaceAll("\\D", "");
int digitLen = digitsOnly.length();
if (digitLen < MIN_FORMAT_LENGTH || digitLen > MAX_FORMAT_LENGTH) {
updatedText = digitsOnly;
return;
}
if (digitLen <= 4) {
String month = digitsOnly.substring(0, 2);
String day = digitsOnly.substring(2);
updatedText = String.format(Locale.US, "%s/%s", month, day);
}
else {
String month = digitsOnly.substring(0, 2);
String day = digitsOnly.substring(2, 4);
String year = digitsOnly.substring(4);
updatedText = String.format(Locale.US, "%s/%s/%s", month, day, year);
}
}
@Override
public void afterTextChanged(Editable editable) {
if (editing) return;
editing = true;
editable.clear();
editable.insert(0, updatedText);
editing = false;
}
}
Вы можете использовать приведенный ниже код, и он также добавляет все проверки даты, чтобы они были действительными. Дней не может быть больше 31; месяц не может быть больше 12 и т. д.
class DateMask : TextWatcher {
private var updatedText: String? = null
private var editing: Boolean = false
companion object {
private const val MAX_LENGTH = 8
private const val MIN_LENGTH = 2
}
override fun beforeTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) {
}
override fun onTextChanged(text: CharSequence, start: Int, before: Int, count: Int) {
if (text.toString() == updatedText || editing) return
var digits = text.toString().replace("\\D".toRegex(), "")
val length = digits.length
if (length <= MIN_LENGTH) {
digits = validateMonth(digits)
updatedText = digits
return
}
if (length > MAX_LENGTH) {
digits = digits.substring(0, MAX_LENGTH)
}
updatedText = if (length <= 4) {
digits = validateDay(digits.substring(0, 2), digits.substring(2))
val month = digits.substring(0, 2)
val day = digits.substring(2)
String.format(Locale.US, "%s/%s", month, day)
} else {
digits = digits.substring(0, 2) + digits.substring(2, 4) + validateYear(digits.substring(4))
val month = digits.substring(0, 2)
val day = digits.substring(2, 4)
val year = digits.substring(4)
String.format(Locale.US, "%s/%s/%s", month, day, year)
}
}
private fun validateDay(month: String, day: String): String {
val arr31 = intArrayOf(1, 3, 5, 7, 8, 10, 12)
val arr30 = intArrayOf(4, 6, 9, 11)
val arrFeb = intArrayOf(2)
if (day.length == 1 &&
((day.toInt() > 3 && month.toInt() !in arrFeb)
|| (day.toInt() > 2 && month.toInt() in arrFeb))) {
return month
}
return when (month.toInt()) {
in arr31 -> validateDay(month, arr31, day, 31)
in arr30 -> validateDay(month, arr30, day, 30)
in arrFeb -> validateDay(month, arrFeb, day, 29)
else -> "$month$day"
}
}
private fun validateDay(month: String, arr: IntArray, day: String, maxDay: Int): String {
if (month.toInt() in arr) {
if (day.toInt() > maxDay) {
return "$month${day.substring(0, 1)}"
}
}
return "$month$day"
}
private fun validateYear(year: String): String {
if (year.length == 1 && (year.toInt() in 3..9 || year.toInt() == 0)) {
return ""
}
if (year.length == 2 && year.toInt() !in 19..20) {
return year.substring(0, 1)
}
return year
}
private fun validateMonth(month: String): String {
if (month.length == 1 && month.toInt() in 2..9) {
return "0$month"
}
if (month.length == 2 && month.toInt() > 12) {
return month.substring(0, 1)
}
return month
}
override fun afterTextChanged(editable: Editable) {
if (editing) return
editing = true
editable.clear()
editable.insert(0, updatedText)
editing = false
}
}
В вашем fragment
или Activity
вы можете использовать это DateMask
как это: mEditText?.addTextChangedListener(dateMask)
Потратил 6 часов на создание собственного формата. Просто попробуйте, и если вам это нравится, пройдите код. Вы можете изменить дату в любом месте, например, только день, только месяц. Он автоматически экранирует символ «/» и обновляет следующую цифру.
// initialized with the current date
String date = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(new Date());
edit_date_editEntity.setText(date);
public void formatDate(){
edit_date_editEntity.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
String ss = s.toString();
if (after==0) { // when a single character is deleted
if (s.charAt(start) == '/') { // if the character is '/' , restore it and put the cursor at correct position
edit_date_editEntity.setText(s);
edit_date_editEntity.setSelection(start);
}
else if (s.charAt(start) == '-') { // if the character is '-' , restore it and put the cursor at correct position
edit_date_editEntity.setText(s);
edit_date_editEntity.setSelection(start);
}
else if (ss.charAt(start) >= '0' && ss.charAt(start) <= '9') { // if the character is a digit, replace it with '-'
ss = ss.substring(0, start) + "-" + ss.substring(start +1, ss.length());
edit_date_editEntity.setText(ss);
edit_date_editEntity.setSelection(start);
}
}
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String ss = s.toString();
if (before==0 ){ // when a single character is added
if (edit_date_editEntity.getSelectionStart()==3 || edit_date_editEntity.getSelectionStart()==6) {
// if the new character was just before '/' character
// getSelection value gets incremented by 1, because text has been changed and hence cursor position updated
// Log.d("test", ss);
ss = ss.substring(0, start) + "/" + ss.substring(start, start + 1) + ss.substring(start + 3, ss.length());
// Log.d("test", ss);
edit_date_editEntity.setText(ss);
edit_date_editEntity.setSelection(start + 2);
}
else {
if (edit_date_editEntity.getSelectionStart()==11){
// if cursor was at last, do not add anything
ss = ss.substring(0,ss.length()-1);
edit_date_editEntity.setText(ss);
edit_date_editEntity.setSelection(10);
}
else {
// else replace the next digit with the entered digit
ss = ss.substring(0, start + 1) + ss.substring(start + 2, ss.length());
edit_date_editEntity.setText(ss);
edit_date_editEntity.setSelection(start + 1);
}
}
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
}