web-dev-qa-db-fra.com

Comment masquer un texte d'édition pour afficher le format de date jj/mm/aaaa

Comment puis-je formater une EditText pour suivre le format "dd/mm/yyyy" de la même manière que nous pouvons formater en utilisant une TextWatcher en masque l'entrée utilisateur pour ressembler à "0,05 €". Je ne parle pas de limiter les caractères, ou de valider une date, mais simplement de masquer au format précédent.

40
Juan Cortés

J'ai écrit ceci TextWatcher pour un projet, j'espère que cela sera utile à quelqu'un. Notez que not ne valide pas la date entrée par l'utilisateur et vous devez gérer cela lorsque le focus change, car l'utilisateur n'a peut-être pas fini d'entrer la date.

Mise à jour 25/06 A créé un wiki pour voir si nous atteignons un meilleur code final.

Mise à jour 07/06 J'ai finalement ajouté une sorte de validation à l'observateur lui-même. Il fera les choses suivantes avec des dates invalides:

  • Si le mois est supérieur à 12, ce sera 12 (décembre)
  • Si la date est supérieure à celle du mois sélectionné, définissez le maximum pour ce mois.
  • Si l'année n'est pas dans la plage 1900-2100, changez-la pour qu'elle soit dans la plage

Cette validation répond à mes besoins, mais certains d'entre vous voudront peut-être la modifier un peu, les plages sont facilement modifiables et vous pouvez accrocher cette validation au message Toast, par exemple, pour informer l'utilisateur que nous avons modifié sa date était invalide.

Dans ce code, je supposerai que nous avons une référence à notre EditText appelée date à laquelle est attachée cette TextWatcher, ceci peut être fait de la manière suivante:

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();

Lorsque l'utilisateur modifie le texte de la 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());
        }
    }

Nous implémentons également les deux autres fonctions car nous devons

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

    @Override
    public void afterTextChanged(Editable s) {}
};

Cela produit l’effet suivant: la suppression ou l’insertion de caractères révèle ou masque le masque dd/mm/yyyy. Il devrait être facile de le modifier pour l’adapter à d’autres masques de format, car j’ai essayé de laisser le code aussi simple que possible.

enter image description here

96
Juan Cortés

La réponse actuelle est très bonne et a permis de me guider vers ma propre solution. J'ai choisi d'afficher ma propre solution pour plusieurs raisons, même si cette question a déjà une réponse valable:

  • Je travaille à Kotlin, pas en Java. Les personnes qui se retrouvent avec le même problème devront traduire la solution actuelle.
  • Je voulais écrire une réponse plus lisible afin que les gens puissent l'adapter plus facilement à leurs propres problèmes.
  • Comme suggéré par dengue8830, j'ai encapsulé la solution à ce problème dans une classe afin que tout le monde puisse l'utiliser sans se soucier de la mise en œuvre.

Pour l'utiliser, il suffit de faire quelque chose comme: 

  • DateInputMask (mEditText) .listen ()

Et la solution est indiquée ci-dessous:

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) {}
    }
}
5
Ícaro Mota

une manière plus propre d'utiliser le code de Juan Cortés est mise dans une classe:

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) {

}
}

alors vous pouvez le réutiliser

new DateInputMask(myEditTextInstance);
4
David Rearte

Le wiki de Juan Cortés fonctionne à merveille https://stackoverflow.com/a/16889503/3480740

Voici ma version 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) {

        }
    })
}
1
ivoroto

Cette réponse n'applique pas de masque complet pour les chiffres non typés restants. Cependant, c'est lié et c'est la solution dont j'avais besoin. Cela fonctionne de la même façon que PhoneNumberFormattingTextWatcher.

Lorsque vous tapez, il ajoute des barres obliques pour séparer une date au format mm/dd/yyyy. Il ne fait aucune validation - juste le formatage.

Pas besoin d'une référence EditText. Il suffit de définir l'auditeur et cela fonctionne .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;
    }
}
0
Markymark