104 votes

Meilleure façon de formater la saisie de devise dans un editText ?

J'ai un editText, la valeur de départ est de 0,00 $. Lorsque vous appuyez sur 1, cela change à 0,01 $. Appuyez sur 4, cela passe à 0,14 $. Appuyez sur 8, 1,48 $. Appuyez sur la touche de suppression, 0,14 $, etc.

Cela fonctionne, le problème est que si quelqu'un positionne manuellement le curseur, des problèmes surviennent dans le formatage. S'ils suppriment le point décimal, il ne reviendra pas. S'ils placent le curseur devant le point décimal et saisissent 2, il affichera 02,00 $ au lieu de 2,00 $. S'ils essaient de supprimer le $, cela supprimera un chiffre à la place, par exemple.

Voici le code que j'utilise, je serais reconnaissant pour toutes suggestions.

mEditPrice.setRawInputType(Configuration.KEYBOARD_12KEY);
    public void priceClick(View view) {
    mEditPrice.addTextChangedListener(new TextWatcher(){
        DecimalFormat dec = new DecimalFormat("0.00");
        @Override
        public void afterTextChanged(Editable arg0) {
        }
        @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().matches("^\\$(\\d{1,3}(\\,\\d{3})*|(\\d+))(\\.\\d{2})?$"))
            {
                String userInput= ""+s.toString().replaceAll("[^\\d]", "");
                if (userInput.length() > 0) {
                    Float in=Float.parseFloat(userInput);
                    float percen = in/100;
                    mEditPrice.setText("$"+dec.format(percen));
                    mEditPrice.setSelection(mEditPrice.getText().length());
                }
            }
        }
    });

7voto

Francisco Junior Points 789

J'ai utilisé l'implémentation référencée par Nathan Leigh et la regex suggérée par Kayvan N et user2582318 pour supprimer tous les caractères sauf les chiffres et créer la version suivante :

fun EditText.addCurrencyFormatter() {

    // Référence : https://stackoverflow.com/questions/5107901/better-way-to-format-currency-input-edittext/29993290#29993290
    this.addTextChangedListener(object: TextWatcher {

        private var current = ""

        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 (s.toString() != current) {
                this@addCurrencyFormatter.removeTextChangedListener(this)
                // supprimer le symbole de la devise

                // Référence pour cette regex de remplacement : https://stackoverflow.com/questions/5107901/better-way-to-format-currency-input-edittext/28005836#28005836
                val cleanString = s.toString().replace("\\D".toRegex(), "")
                val parsed = if (cleanString.isBlank()) 0.0 else cleanString.toDouble()
                // formatter le double dans un format de devise
                val formated = NumberFormat.getCurrencyInstance()
                        .format(parsed / 100)

                current = formated
                this@addCurrencyFormatter.setText(formated)
                this@addCurrencyFormatter.setSelection(formated.length)

                this@addCurrencyFormatter.addTextChangedListener(this)
            }
        }
    })

}

Ceci est une fonction d'extension en Kotlin qui ajoute le TextWatcher à l'écouteur de modification de texte de l'EditText.

Pour l'utiliser, il suffit de :

yourEditText = (EditText) findViewById(R.id.edit_text_your_id);
yourEditText.addCurrencyFormatter()

J'espère que cela vous aidera.

5voto

Kayvan N Points 3299

Même s'il y a beaucoup de réponses ici, je voudrais partager ce code que j'ai trouvé ici car je crois que c'est la réponse la plus robuste et propre.

class CurrencyTextWatcher implements TextWatcher {

    boolean mEditing;

    public CurrencyTextWatcher() {
        mEditing = false;
    }

    public synchronized void afterTextChanged(Editable s) {
        if(!mEditing) {
            mEditing = true;

            String digits = s.toString().replaceAll("\\D", "");
            NumberFormat nf = NumberFormat.getCurrencyInstance();
            try{
                String formatted = nf.format(Double.parseDouble(digits)/100);
                s.replace(0, s.length(), formatted);
            } catch (NumberFormatException nfe) {
                s.clear();
            }

            mEditing = false;
        }
    }

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

    public void onTextChanged(CharSequence s, int start, int before, int count) { }

}

j'espère que cela aide.

4voto

miguelt Points 71

Ok, voici une meilleure façon de gérer les formats de devise, la suppression de l'entrée en arrière. Le code est basé sur le code de @androidcurious ci-dessus... Mais, traite certains problèmes liés à la suppression en arrière et certaines exceptions d'analyse : http://miguelt.blogspot.ca/2013/01/textwatcher-for-currency-masksformatting.html [MISE À JOUR] La solution précédente avait certains problèmes... Voici une meilleure solution : http://miguelt.blogspot.ca/2013/02/update-textwatcher-for-currency.html Et... voici les détails :

Cette approche est meilleure car elle utilise les mécanismes Android conventionnels. L'idée est de formater les valeurs après que l'utilisateur quitte la vue.

Définir un InputFilter pour restreindre les valeurs numériques - cela est nécessaire dans la plupart des cas car l'écran n'est pas assez grand pour accueillir de longues vues EditText. Cela peut être une classe interne statique ou juste une autre classe simple :

/** Filtre de plage numérique. */
class NumericRangeFilter implements InputFilter {
    /** Valeur maximale. */
    private final double maximum;
    /** Valeur minimale. */
    private final double minimum;
    /** Crée un nouveau filtre entre 0.00 et 999 999.99. */
    NumericRangeFilter() {
        this(0.00, 999999.99);
    }
    /** Crée un nouveau filtre.
     * @param p_min Valeur minimale.
     * @param p_max Valeur maximale. 
     */
    NumericRangeFilter(double p_min, double p_max) {
        maximum = p_max;
        minimum = p_min;
    }
    @Override
    public CharSequence filter(
            CharSequence p_source, int p_start,
            int p_end, Spanned p_dest, int p_dstart, int p_dend
    ) {
        try {
            String v_valueStr = p_dest.toString().concat(p_source.toString());
            double v_value = Double.parseDouble(v_valueStr);
            if (v_value<=maximum && v_value>=minimum) {
                // Retourner null permettra à EditText d'accepter plus de valeurs.
                return null;
            }
        } catch (NumberFormatException p_ex) {
            // ne rien faire
        }
        // La valeur est hors de la plage - retourner une chaîne vide.
        return "";
    }
}

Définir une classe (statique interne ou juste une classe) qui implémentera View.OnFocusChangeListener. Notez que j'utilise une classe Utils - l'implémentation peut être trouvée à "Amounts, Taxes".

/** Utilisé pour formater les vues de montant. */
class AmountOnFocusChangeListener implements View.OnFocusChangeListener {
    @Override
    public void onFocusChange(View p_view, boolean p_hasFocus) {
        // Ce listener sera attaché à toute vue contenant des montants.
        EditText v_amountView = (EditText)p_view;
        if (p_hasFocus) {
            // v_value utilise un masque de devise - le transformer en cents.
            String v_value = v_amountView.getText().toString();
            int v_cents = Utils.parseAmountToCents(v_value);
            // Maintenant, formater les cents en un montant (sans masque de devise)
            v_value = Utils.formatCentsToAmount(v_cents);
            v_amountView.setText(v_value);
            // Sélectionner tout afin que l'utilisateur puisse écraser tout le montant en une seule fois.
            v_amountView.selectAll();
        } else {
            // v_value n'utilise pas de masque de devise - le transformer en cents.
            String v_value = v_amountView.getText().toString();
            int v_cents = Utils.parseAmountToCents(v_value);
            // Maintenant, formater les cents en un montant (avec masque de devise)
            v_value = Utils.formatCentsToCurrency(v_cents);
            v_amountView.setText(v_value);
        }
    }
}

Cette classe supprimera le format de devise lors de l'édition - en s'appuyant sur des mécanismes standard. Lorsque l'utilisateur quitte, le format de devise est réappliqué.

Il est préférable de définir quelques variables statiques pour minimiser le nombre d'instances :

   static final InputFilter[] FILTERS = new InputFilter[] {new NumericRangeFilter()};
   static final View.OnFocusChangeListener ON_FOCUS = new AmountOnFocusChangeListener();

Enfin, dans le onCreateView(...):

   EditText mAmountView = ....
   mAmountView.setFilters(FILTERS);
   mAmountView.setOnFocusChangeListener(ON_FOCUS);

Vous pouvez réutiliser FILTERS et ON_FOCUS sur autant de vues EditText que nécessaire.

Voici la classe Utils :

public class Utils {

   private static final NumberFormat FORMAT_CURRENCY = NumberFormat.getCurrencyInstance();
   /** Analyse un montant en cents.
    * @param p_value Montant formaté en utilisant la devise par défaut. 
    * @return Valeur en cents.
    */
   public static int parseAmountToCents(String p_value) {
       try {
           Number v_value = FORMAT_CURRENCY.parse(p_value);
           BigDecimal v_bigDec = new BigDecimal(v_value.doubleValue());
           v_bigDec = v_bigDec.setScale(2, BigDecimal.ROUND_HALF_UP);
           return v_bigDec.movePointRight(2).intValue();
       } catch (ParseException p_ex) {
           try {
               // p_value n'a pas de format de devise.
               BigDecimal v_bigDec = new BigDecimal(p_value);
               v_bigDec = v_bigDec.setScale(2, BigDecimal.ROUND_HALF_UP);
               return v_bigDec.movePointRight(2).intValue();
           } catch (NumberFormatException p_ex1) {
               return -1;
           }
       }
   }
   /** Formate les cents en un montant valide en utilisant la devise par défaut.
    * @param p_value Valeur en cents 
    * @return Montant formaté en utilisant une devise.
    */
   public static String formatCentsToAmount(int p_value) {
       BigDecimal v_bigDec = new BigDecimal(p_value);
       v_bigDec = v_bigDec.setScale(2, BigDecimal.ROUND_HALF_UP);
       v_bigDec = v_bigDec.movePointLeft(2);
       String v_currency = FORMAT_CURRENCY.format(v_bigDec.doubleValue());
       return v_currency.replace(FORMAT_CURRENCY.getCurrency().getSymbol(), "").replace(",", "");
   }
   /** Formate les cents en un montant valide en utilisant la devise par défaut.
    * @param p_value Valeur en cents 
    * @return Montant formaté en utilisant une devise.
    */
   public static String formatCentsToCurrency(int p_value) {
       BigDecimal v_bigDec = new BigDecimal(p_value);
       v_bigDec = v_bigDec.setScale(2, BigDecimal.ROUND_HALF_UP);
       v_bigDec = v_bigDec.movePointLeft(2);
       return FORMAT_CURRENCY.format(v_bigDec.doubleValue());
   }

}

4voto

genixpro Points 31

J'ai construit sur la réponse de Guilherme, mais je conserve la position du curseur et je traite également les points différemment - de cette façon, si un utilisateur tape après le point, cela n'affecte pas les chiffres avant le point. Je trouve que cela donne une saisie très fluide.

    [yourtextfield].addTextChangedListener(new TextWatcher()
    {
        NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
        private String current = "";

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count)
        {
            if(!s.toString().equals(current))
            {
                   [yourtextfield].removeTextChangedListener(this);

                   int selection = [yourtextfield].getSelectionStart();

                   // Nous enlevons le symbole de la devise
                   String replaceable = String.format("[%s,\\s]", NumberFormat.getCurrencyInstance().getCurrency().getSymbol());
                   String cleanString = s.toString().replaceAll(replaceable, "");

                   double price;

                   // Analyser la chaîne                     
                   try
                   {
                       price = Double.parseDouble(cleanString);
                   }
                   catch(java.lang.NumberFormatException e)
                   {
                       price = 0;
                   }

                   // Si nous ne voyons pas de décimal, alors l'utilisateur doit l'avoir supprimé.
                   // Dans ce cas, le nombre doit être divisé par 100, sinon par 1
                   int shrink = 1;
                   if(!(s.toString().contains(".")))
                   {
                       shrink = 100;
                   }

                   // Reformater le nombre
                   String formated = currencyFormat.format((price / shrink));

                   current = formated;
                   [yourtextfield].setText(formated);
                   [yourtextfield].setSelection(Math.min(selection, [yourtextfield].getText().length()));

                   [yourtextfield].addTextChangedListener(this);
                }
        }

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

        }

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

3voto

Nuno Monteiro Points 31

J'ai trouvé cela ici et je l'ai modifié pour être conforme au format de devise portugais.

import java.text.NumberFormat;
import java.util.Currency;
import java.util.Locale;

import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;

public class CurrencyTextWatcher implements TextWatcher {

    private String current = "";
    private int index;
    private boolean deletingDecimalPoint;
    private final EditText currency;

    public CurrencyTextWatcher(EditText p_currency) {
        currency = p_currency;
    }

    @Override
    public void beforeTextChanged(CharSequence p_s, int p_start, int p_count, int p_after) {

        if (p_after>0) {
                index = p_s.length() - p_start;
            } else {
                index = p_s.length() - p_start - 1;
            }
            if (p_count>0 && p_s.charAt(p_start)==',') {
                deletingDecimalPoint = true;
            } else {
                deletingDecimalPoint = false;
            }

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable p_s) {

         if(!p_s.toString().equals(current)){
                currency.removeTextChangedListener(this);
                if (deletingDecimalPoint) {
                    p_s.delete(p_s.length()-index-1, p_s.length()-index);
                }
                // Currency char may be retrieved from  NumberFormat.getCurrencyInstance()
                String v_text = p_s.toString().replace("€","").replace(",", "");
                v_text = v_text.replaceAll("\\s", "");
                double v_value = 0;
                if (v_text!=null && v_text.length()>0) {
                    v_value = Double.parseDouble(v_text);
                }
                // Currency instance may be retrieved from a static member.
                NumberFormat numberFormat = NumberFormat.getCurrencyInstance(new Locale("pt", "PT"));
                String v_formattedValue = numberFormat.format((v_value/100));
                current = v_formattedValue;
                currency.setText(v_formattedValue);
                if (index>v_formattedValue.length()) {
                    currency.setSelection(v_formattedValue.length());
                } else {
                    currency.setSelection(v_formattedValue.length()-index);
                }
                // include here anything you may want to do after the formatting is completed.
                currency.addTextChangedListener(this);
             }
    }

}

Le layout.xml

Faites-le fonctionner

    yourEditText = (EditText) findViewById(R.id.edit_text_your_id);
    yourEditText.setRawInputType(Configuration.KEYBOARD_12KEY);
    yourEditText.addTextChangedListener(new CurrencyTextWatcher(yourEditText));

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X