Как замаскировать EditText для отображения формата даты дд/мм/гггг

Как я могу отформатировать EditText так, чтобы он соответствовал формату «dd/mm/yyyy» так же, как мы можем отформатировать с помощью TextWatcher, чтобы маскировать пользовательский ввод, чтобы он выглядел как «0,05€». Я не говорю об ограничении символов или проверке даты, просто маскируя предыдущий формат.


person Juan Cortés    schedule 03.06.2013    source источник


Ответы (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. Его должно быть легко изменить, чтобы он соответствовал маскам других форматов, поскольку я старался сделать код как можно более простым.

введите здесь описание изображения

person Community    schedule 03.06.2013
comment
Мне жаль. Как я могу получить доступ к полному коду? Какая у вас переменная cal? - person joao2fast4u; 04.04.2014
comment
@joao2fast4u - переменная cal является объектом java.util.Calendar - person Leonardo Otto; 30.09.2014
comment
@joao2fast4u @LeonardoOtto Для таких новичков, как я: у меня были трудные времена, пока я не объявил cal следующим образом: private Calendar cal = Calendar.getInstance(); иначе я получил бы NullPointerException в части cal.set(Calendar.MONTH, mon-1); - person Thomas; 22.01.2015
comment
Извините за то, что код не является новым, если у кого-то есть время отредактировать его, пожалуйста, не стесняйтесь. Вот почему я сделал это вики - person Juan Cortés; 22.01.2015
comment
@Juan-devtopia.coop Ты прав. Я сделал изменение. Спасибо за код, очень помог. - person Thomas; 22.01.2015
comment
Но как настроить EditText для отображения структуры dd/dd/dddd, где d — цифра, а «/» — разделитель, которые остаются на экране и не могут быть отредактированы? а может это другой вопрос? - person GyRo; 03.02.2015
comment
Это еще один вопрос для меня @GuyRoth :/ - person Juan Cortés; 03.02.2015
comment
Код работает, но иногда он создает строку, подобную этой 12/12/1999Y. Я не понимаю, почему он добавляет Y в конец последней строки. - person ad3luc; 17.02.2016
comment
Можете ли вы помочь мне в случае, когда мы набрали всю дату, скажем, 24/12/2015 и удалили 4, тогда весь текст после 4 сдвигается влево. Есть ли способ избежать этого? - person DAS; 17.12.2016
comment
Всегда есть способ. Узнайте, что изменилось по сравнению с предыдущей версией значения, и если мы изменили что-то кроме конца, заполните пробел, скажем... нулями. Как это сделать, это отдельная история, возможно, подумайте о том, чтобы посмотреть на позицию курсора... Или более уродливым UX было бы всегда иметь курсор в конце.. - person Juan Cortés; 21.12.2016
comment
В моем случае (формат даты дд.мм.гггг вместо дд/мм/гггг) это сработало только после этого редактирования: String clean = s.toString().replaceAll(\\D, ); Строка cleanC = current.replaceAll(\\D, ); Первоначальная версия производила избыточные точки каждый раз, когда пользователь набирал символ, только потому, что прежнее регулярное выражение не устраняло их. Я думаю, что он ошибочно принимает точки за десятичные точки, которые являются частью чисел. - person 12oz Mouse; 06.06.2017
comment
какой ток!? это предыдущее содержимое EditText или текущее? В коде есть некоторые непонятные и запутанные переменные, например: current, ddmmyyyy и т. д. вы сделали это вики, пожалуйста, уточните свой код - person null; 31.10.2017
comment
В моей системе есть открытая ошибка против вашего кода. Невозможно удалить числа из центра даты... вся строка сдвигается влево. Например, если при заданной строке 23/11/2016 я попытаюсь удалить из строки только 11, то получу частичную дату 23/20/16YY. - person QED; 22.02.2018
comment
Добавьте проверку на 01.00.1900 (т.е. cal.getActualMinimum()) замените это: 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
comment
Я меняю формат даты на ГГГГММДД. Мне удается работать, но я не могу удалить часть года из текста редактирования - person D.madushanka; 10.12.2020

Текущий ответ очень хорош и помог мне найти собственное решение. Есть несколько причин, по которым я решил опубликовать собственное решение, хотя на этот вопрос уже есть правильный ответ:

  • Я работаю на Котлине, а не на 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) {}
    }
}
person Ícaro Mota    schedule 09.10.2017

более чистый способ использования кода Хуана Кортеса - поместить его в класс:

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);
person David Rearte    schedule 19.09.2017

Попробуйте использовать библиотеку, которая решает эту проблему, так как маскирование недоступно из коробки. Есть много угловых случаев (например, добавление/удаление символов в середине уже замаскированного текста), и для правильной обработки этого вы получите много кода (и ошибок).

Вот некоторые доступные библиотеки:
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

** Имейте в виду, что на момент написания этих библиотек были проблемы, поэтому вы должны выбрать, какая из них подходит вам лучше всего, и протестировать код.

person kalimero    schedule 16.01.2018

Вики Хуана Кортеса работает как шарм 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) {

        }
    })
}
person ivoroto    schedule 18.01.2019
comment
Это работает, было бы хорошо определить метод как функцию расширения в классе Edittext. - person Nicola Gallazzi; 19.09.2019
comment
Также вы пропустили одну вещь 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)

        }
    })
person Saurabh Padwekar    schedule 26.04.2019

добавить android:inputType="date" к EditText

person Alireza Jamali    schedule 23.05.2019

Этот ответ не применяет полную маску для оставшихся нетипизированных цифр. Тем не менее, это связано и является решением, в котором я нуждался. Он работает аналогично тому, как работает 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;
    }
}
person Markymark    schedule 05.12.2017

Вы можете использовать приведенный ниже код, и он также добавляет все проверки даты, чтобы они были действительными. Дней не может быть больше 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)

person Ranjeet    schedule 28.09.2019
comment
идеальное рабочее очарование - person ABDUL RAHMAN; 29.07.2021

Потратил 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) {

        }
    });
}
person Avinash Kumar    schedule 22.02.2021