web-dev-qa-db-fra.com

Virgule séparatrice décimale (',') avec nombre Entrée décimale Tapez dans EditText

La inputTypenumberDecimal dans EditText utilise le point '.' comme séparateur décimal. En Europe, il est courant d'utiliser une virgule ',' à la place. Même si mes paramètres régionaux sont définis en allemand, le séparateur décimal reste le '.'

Y a-t-il un moyen d'obtenir la virgule comme séparateur décimal?

103
mseo

Une solution de contournement (jusqu'à ce que Google corrige ce bogue) consiste à utiliser un EditText avec Android:inputType="numberDecimal" et Android:digits="0123456789.,"

Ajoutez ensuite un TextChangedListener à EditText avec l’après afterTextChanged:

public void afterTextChanged(Editable s) {
    double doubleValue = 0;
    if (s != null) {
        try {
            doubleValue = Double.parseDouble(s.toString().replace(',', '.'));
        } catch (NumberFormatException e) {
            //Error
        }
    }
    //Do something with doubleValue
}
91
Martin

Une variante des solutions "digit" proposées ici:

char separator = DecimalFormatSymbols.getInstance().getDecimalSeparator();
input.setKeyListener(DigitsKeyListener.getInstance("0123456789" + separator));

Prise en compte du séparateur de paramètres régionaux.

25
dstibbe

Masque de code monnaie pour EditText (123 125,155 USD)

Mise en page xml

  <EditText
    Android:inputType="numberDecimal"
    Android:layout_height="wrap_content"
    Android:layout_width="200dp"
    Android:digits="0123456789.,$" />

Code

EditText testFilter=...
testFilter.addTextChangedListener( new TextWatcher() {
        boolean isEdiging;
        @Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
        @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }

        @Override public void afterTextChanged(Editable s) {
            if(isEdiging) return;
            isEdiging = true;

            String str = s.toString().replaceAll( "[^\\d]", "" );
            double s1 = Double.parseDouble(str);

            NumberFormat nf2 = NumberFormat.getInstance(Locale.ENGLISH);
            ((DecimalFormat)nf2).applyPattern("$ ###,###.###");
            s.replace(0, s.length(), nf2.format(s1));

            isEdiging = false;
        }
    });
17
user1269737

Il s'agit d'un bogue connu dans le SDK Android . La seule solution consiste à créer votre propre clavier logiciel. Vous pouvez trouver un exemple de mise en oeuvre ici .

15
EricLarch

La réponse de Martins ne fonctionnera pas si vous instanciez le EditText par programme. Je suis allé de l'avant et j'ai modifié la classe DigitsKeyListener incluse de l'API 14 pour permettre à la fois une virgule et une période comme séparateur décimal.

Pour utiliser cela, appelez setKeyListener() sur la EditText, par exemple.

// Don't allow for signed input (minus), but allow for decimal points
editText.setKeyListener( new MyDigitsKeyListener( false, true ) );

Cependant, vous devez toujours utiliser l'astuce de Martin dans la variable TextChangedListener où vous remplacez les virgules par des points.

import Android.text.InputType;
import Android.text.SpannableStringBuilder;
import Android.text.Spanned;
import Android.text.method.NumberKeyListener;
import Android.view.KeyEvent;

class MyDigitsKeyListener extends NumberKeyListener {

    /**
     * The characters that are used.
     *
     * @see KeyEvent#getMatch
     * @see #getAcceptedChars
     */
    private static final char[][] CHARACTERS = new char[][] {
        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' },
        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ',' },
        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.', ',' },
    };

    private char[] mAccepted;
    private boolean mSign;
    private boolean mDecimal;

    private static final int SIGN = 1;
    private static final int DECIMAL = 2;

    private static MyDigitsKeyListener[] sInstance = new MyDigitsKeyListener[4];

    @Override
    protected char[] getAcceptedChars() {
        return mAccepted;
    }

    /**
     * Allocates a DigitsKeyListener that accepts the digits 0 through 9.
     */
    public MyDigitsKeyListener() {
        this(false, false);
    }

    /**
     * Allocates a DigitsKeyListener that accepts the digits 0 through 9,
     * plus the minus sign (only at the beginning) and/or decimal point
     * (only one per field) if specified.
     */
    public MyDigitsKeyListener(boolean sign, boolean decimal) {
        mSign = sign;
        mDecimal = decimal;

        int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
        mAccepted = CHARACTERS[kind];
    }

    /**
     * Returns a DigitsKeyListener that accepts the digits 0 through 9.
     */
    public static MyDigitsKeyListener getInstance() {
        return getInstance(false, false);
    }

    /**
     * Returns a DigitsKeyListener that accepts the digits 0 through 9,
     * plus the minus sign (only at the beginning) and/or decimal point
     * (only one per field) if specified.
     */
    public static MyDigitsKeyListener getInstance(boolean sign, boolean decimal) {
        int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);

        if (sInstance[kind] != null)
            return sInstance[kind];

        sInstance[kind] = new MyDigitsKeyListener(sign, decimal);
        return sInstance[kind];
    }

    /**
     * Returns a DigitsKeyListener that accepts only the characters
     * that appear in the specified String.  Note that not all characters
     * may be available on every keyboard.
     */
    public static MyDigitsKeyListener getInstance(String accepted) {
        // TODO: do we need a cache of these to avoid allocating?

        MyDigitsKeyListener dim = new MyDigitsKeyListener();

        dim.mAccepted = new char[accepted.length()];
        accepted.getChars(0, accepted.length(), dim.mAccepted, 0);

        return dim;
    }

    public int getInputType() {
        int contentType = InputType.TYPE_CLASS_NUMBER;
        if (mSign) {
            contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
        }
        if (mDecimal) {
            contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
        }
        return contentType;
    }

    @Override
    public CharSequence filter(CharSequence source, int start, int end,
                               Spanned dest, int dstart, int dend) {
        CharSequence out = super.filter(source, start, end, dest, dstart, dend);

        if (mSign == false && mDecimal == false) {
            return out;
        }

        if (out != null) {
            source = out;
            start = 0;
            end = out.length();
        }

        int sign = -1;
        int decimal = -1;
        int dlen = dest.length();

        /*
         * Find out if the existing text has '-' or '.' characters.
         */

        for (int i = 0; i < dstart; i++) {
            char c = dest.charAt(i);

            if (c == '-') {
                sign = i;
            } else if (c == '.' || c == ',') {
                decimal = i;
            }
        }
        for (int i = dend; i < dlen; i++) {
            char c = dest.charAt(i);

            if (c == '-') {
                return "";    // Nothing can be inserted in front of a '-'.
            } else if (c == '.' ||  c == ',') {
                decimal = i;
            }
        }

        /*
         * If it does, we must strip them out from the source.
         * In addition, '-' must be the very first character,
         * and nothing can be inserted before an existing '-'.
         * Go in reverse order so the offsets are stable.
         */

        SpannableStringBuilder stripped = null;

        for (int i = end - 1; i >= start; i--) {
            char c = source.charAt(i);
            boolean strip = false;

            if (c == '-') {
                if (i != start || dstart != 0) {
                    strip = true;
                } else if (sign >= 0) {
                    strip = true;
                } else {
                    sign = i;
                }
            } else if (c == '.' || c == ',') {
                if (decimal >= 0) {
                    strip = true;
                } else {
                    decimal = i;
                }
            }

            if (strip) {
                if (end == start + 1) {
                    return "";  // Only one character, and it was stripped.
                }

                if (stripped == null) {
                    stripped = new SpannableStringBuilder(source, start, end);
                }

                stripped.delete(i - start, i + 1 - start);
            }
        }

        if (stripped != null) {
            return stripped;
        } else if (out != null) {
            return out;
        } else {
            return null;
        }
    }
}
6
JesperB

Vous pouvez utiliser la solution de contournement suivante pour inclure également une virgule en tant qu'entrée valide: -

À travers XML:

<EditText
    Android:inputType="number"
    Android:digits="0123456789.," />

Par programme:

EditText input = new EditText(THE_CONTEXT);
input.setKeyListener(DigitsKeyListener.getInstance("0123456789.,"));

De cette façon, le système Android affichera le clavier des chiffres et autorisera la saisie d'une virgule. J'espère que cela répond à la question :)

5
MiaN KhaLiD

vous pouvez utiliser les éléments suivants pour différents lieux

private void localeDecimalInput(final EditText editText){

    DecimalFormat decFormat = (DecimalFormat) DecimalFormat.getInstance(Locale.getDefault());
    DecimalFormatSymbols symbols=decFormat.getDecimalFormatSymbols();
    final String defaultSeperator=Character.toString(symbols.getDecimalSeparator());

    editText.addTextChangedListener(new TextWatcher() {

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

        }

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

        }

        @Override
        public void afterTextChanged(Editable editable) {
            if(editable.toString().contains(defaultSeperator))
                editText.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
            else
                editText.setKeyListener(DigitsKeyListener.getInstance("0123456789" + defaultSeperator));
        }
    });
}
2
Ismail

Pour les solutions Mono (Droid):

decimal decimalValue = decimal.Parse(input.Text.Replace(",", ".") , CultureInfo.InvariantCulture);
2
Ludwo

Vous pouvez faire ce qui suit:

DecimalFormatSymbols d = DecimalFormatSymbols.getInstance(Locale.getDefault());
input.setFilters(new InputFilter[] { new DecimalDigitsInputFilter(5, 2) });
input.setKeyListener(DigitsKeyListener.getInstance("0123456789" + d.getDecimalSeparator()));

Et ensuite, vous pourriez utiliser un filtre d'entrée:

    public class DecimalDigitsInputFilter implements InputFilter {

Pattern mPattern;

public DecimalDigitsInputFilter(int digitsBeforeZero, int digitsAfterZero) {
    DecimalFormatSymbols d = new DecimalFormatSymbols(Locale.getDefault());
    String s = "\\" + d.getDecimalSeparator();
    mPattern = Pattern.compile("[0-9]{0," + (digitsBeforeZero - 1) + "}+((" + s + "[0-9]{0," + (digitsAfterZero - 1) + "})?)||(" + s + ")?");
}

@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {

    Matcher matcher = mPattern.matcher(dest);
    if (!matcher.matches())
        return "";
    return null;
}

}

1
Pablo

pour localiser votre utilisation d'entrée:

char sep = DecimalFormatSymbols.getInstance().getDecimalSeparator();

puis ajoutez:

textEdit.setKeyListener(DigitsKeyListener.getInstance("0123456789" + sep));

que ne pas oublier de remplacer "," par "." donc Float ou Double peuvent l’analyser sans erreur.

1
Milos Simic Simo

Ma solution est:

  • En activité principale:

    char separator =DecimalFormatSymbols.getInstance().getDecimalSeparator(); textViewPitchDeadZone.setKeyListener(DigitsKeyListener.getInstance("0123456789" + separator));

  • En fichier xml: Android:imeOptions="flagNoFullscreen" Android:inputType="numberDecimal"

et j'ai pris le double dans le editText comme une chaîne.

0
Lseb

Je peux confirmer que les correctifs proposés ne fonctionnent pas sur les IME Samsung (au moins sur les S6 et S9) et peut-être sur LG. Ils affichent toujours un point comme séparateur décimal, indépendamment de la localisation. Le passage à l'IME de Google résout ce problème mais n'est guère une option pour la plupart des développeurs.

Il n’a pas non plus été corrigé dans Oreo pour ces claviers, car c’est un correctif que Samsung et/ou LG doivent faire, puis pousser même leurs anciens combinés.

J'ai plutôt ajouté le projet clavier numérique et ajouté un mode dans lequel il se comporte comme un IME: fork . Voir l'exemple de projet pour plus de détails. Cela a très bien fonctionné pour moi et ressemble à la plupart des faux IME "Saisie du code confidentiel" que vous voyez dans les applications bancaires.

Sample app screenshot

0
Kevin Read

Solution simple, créez un contrôle personnalisé. (Ceci est fait dans Android Xamarin mais devrait porter facilement à Java)

public class EditTextDecimalNumber:EditText
{
    readonly string _numberFormatDecimalSeparator;

    public EditTextDecimalNumber(Context context, IAttributeSet attrs) : base(context, attrs)
    {
        InputType = InputTypes.NumberFlagDecimal;
        TextChanged += EditTextDecimalNumber_TextChanged;
        _numberFormatDecimalSeparator = System.Threading.Thread.CurrentThread.CurrentUICulture.NumberFormat.NumberDecimalSeparator;

        KeyListener = DigitsKeyListener.GetInstance($"0123456789{_numberFormatDecimalSeparator}");
    }

    private void EditTextDecimalNumber_TextChanged(object sender, TextChangedEventArgs e)
    {
        int noOfOccurence = this.Text.Count(x => x.ToString() == _numberFormatDecimalSeparator);
        if (noOfOccurence >=2)
        {
            int lastIndexOf = this.Text.LastIndexOf(_numberFormatDecimalSeparator,StringComparison.CurrentCulture);
            if (lastIndexOf!=-1)
            {
                this.Text = this.Text.Substring(0, lastIndexOf);
                this.SetSelection(this.Text.Length);
            }

        }
    }
}
0
Nicolai

Android a un formateur de numéros intégré. 

Vous pouvez ajouter ceci à votre EditText pour permettre les décimales et les virgules: Android:inputType="numberDecimal" et Android:digits="0123456789.,".

Puis quelque part dans votre code, lorsque l'utilisateur clique sur Enregistrer ou après la saisie du texte (utilisez un écouteur). 

// Format the number to the appropriate double
try { 
    Number formatted = NumberFormat.getInstance().parse(editText.getText().toString());
    cost = formatted.doubleValue();
} catch (ParseException e) {
    System.out.println("Error parsing cost string " + editText.getText().toString());
    cost = 0.0;
}
0
Luis

IMHO la meilleure approche pour ce problème est d'utiliser simplement le InputFilter. Voici un fichier Nice Gist/ DecimalDigitsInputFilter . Ensuite, vous pouvez simplement:

editText.setInputType(TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED | TYPE_CLASS_NUMBER)
editText.setKeyListener(DigitsKeyListener.getInstance("0123456789,.-"))
editText.setFilters(new InputFilter[] {new DecimalDigitsInputFilter(5,2)});

Je pense que cette solution est moins complexe que les autres écrites ici:

<EditText
    Android:inputType="numberDecimal"
    Android:digits="0123456789," />

De cette façon, lorsque vous appuyez sur la touche '.' dans le clavier logiciel rien ne se passe; seuls les chiffres et les virgules sont autorisés.

0

Tous les autres postes ici présentaient des lacunes majeures. Voici donc une solution qui:

  • Appliquer des virgules ou des points en fonction de la région ne vous laissera pas taper le contraire.
  • Si EditText commence par une valeur, il remplace le séparateur correct si nécessaire.

dans le XML:

<EditText
    ...
    Android:inputType="numberDecimal" 
    ... />

Variable de classe:

private boolean isDecimalSeparatorComma = false;

Dans onCreate, recherchez le séparateur utilisé dans les paramètres régionaux actuels:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    NumberFormat nf = NumberFormat.getInstance();
    if (nf instanceof DecimalFormat) {
        DecimalFormatSymbols sym = ((DecimalFormat) nf).getDecimalFormatSymbols();
        char decSeparator = sym.getDecimalSeparator();
        isDecimalSeparatorComma = Character.toString(decSeparator).equals(",");
    }
}

OnCreate également, utilisez ceci pour le mettre à jour si vous chargez une valeur actuelle:

// Replace editText with commas or periods as needed for viewing
String editTextValue = getEditTextValue(); // load your current value
if (editTextValue.contains(".") && isDecimalSeparatorComma) {
    editTextValue = editTextValue.replaceAll("\\.",",");
} else if (editTextValue.contains(",") && !isDecimalSeparatorComma) {
    editTextValue = editTextValue.replaceAll(",",".");
}
setEditTextValue(editTextValue); // override your current value

également sur Créer, Ajouter les auditeurs

editText.addTextChangedListener(editTextWatcher);

if (isDecimalSeparatorComma) {
    editText.setKeyListener(DigitsKeyListener.getInstance("0123456789,"));
} else {
    editText.setKeyListener(DigitsKeyListener.getInstance("0123456789."));
}

editTextWatcher

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

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

    @Override
    public void afterTextChanged(Editable s) {
        String editTextValue = s.toString();

        // Count up the number of commas and periods
        Pattern pattern = Pattern.compile("[,.]");
        Matcher matcher = pattern.matcher(editTextValue);
        int count = 0;
        while (matcher.find()) {
            count++;
        }

        // Don't let it put more than one comma or period
        if (count > 1) {
            s.delete(s.length()-1, s.length());
        } else {
            // If there is a comma or period at the end the value hasn't changed so don't update
            if (!editTextValue.endsWith(",") && !editTextValue.endsWith(".")) {
                doSomething()
            }
        }
    }
};

exemple: Quelque chose (), conversion en période standard pour la manipulation de données

private void doSomething() {
    try {
        String editTextStr = editText.getText().toString();
        if (isDecimalSeparatorComma) {
            editTextStr = editTextStr.replaceAll(",",".");
        }
        float editTextFloatValue = editTextStr.isEmpty() ?
                0.0f :
                Float.valueOf(editTextStr);

        ... use editTextFloatValue
    } catch (NumberFormatException e) {
        Log.e(TAG, "Error converting String to Double");
    }
}
0
Eli Fry

J'ai décidé de changer la virgule en point uniquement lors de l'édition. Voici ma solution de contournement simple et complexe: 

    editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            EditText editText = (EditText) v; 
            String text = editText.getText().toString();
            if (hasFocus) {
                editText.setText(text.replace(",", "."));
            } else {
                if (!text.isEmpty()) {
                    Double doubleValue = Double.valueOf(text.replace(",", "."));
                    editText.setText(someDecimalFormatter.format(doubleValue));
                }
            }
        }
    });

someDecimalFormatter utilisera une virgule ou un point dépendant de la locale

0
KaMyLL

Plus de 8 ans se sont écoulés et je suis surpris que ce problème ne soit pas encore résolu ...
Je me débattais avec ce problème simple depuis La réponse la plus votée par @Martin permet de taper plusieurs séparateurs, c’est-à-dire que l’utilisateur peut taper "12 , 12,1, 21,2,"
En outre, le second problème concerne le fait que, sur certains périphériques, virgule n'apparaît pas sur le clavier numérique (ou nécessite d'appuyer plusieurs fois sur un bouton à pois). 

Voici ma solution de contournement, qui résout les problèmes mentionnés et permet à l'utilisateur de taper '.' et ',', mais dans EditText, il verra le seul séparateur décimal qui correspond à la localisation actuelle:

editText.apply { addTextChangedListener(DoubleTextChangedListener(this)) }

Et l'observateur de texte:

  open class DoubleTextChangedListener(private val et: EditText) : TextWatcher {

    init {
        et.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
        et.keyListener = DigitsKeyListener.getInstance("0123456789.,")
    }

    private val separator = DecimalFormatSymbols.getInstance().decimalSeparator

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        //empty
    }

    @CallSuper
    override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
        et.run {
            removeTextChangedListener(this@DoubleTextChangedListener)
            val formatted = toLocalizedDecimal(s.toString(), separator)
            setText(formatted)
            setSelection(formatted.length)
            addTextChangedListener(this@DoubleTextChangedListener)
        }
    }

    override fun afterTextChanged(s: Editable?) {
        // empty
    }

    /**
     * Formats input to a decimal. Leaves the only separator (or none), which matches [separator].
     * Examples:
     * 1. [s]="12.12", [separator]=',' -> result= "12,12"
     * 2. [s]="12.12", [separator]='.' -> result= "12.12"
     * 4. [s]="12,12", [separator]='.' -> result= "12.12"
     * 5. [s]="12,12,,..,,,,,34..,", [separator]=',' -> result= "12,1234"
     * 6. [s]="12.12,,..,,,,,34..,", [separator]='.' -> result= "12.1234"
     * 7. [s]="5" -> result= "5"
     */
    private fun toLocalizedDecimal(s: String, separator: Char): String {
        val cleared = s.replace(",", ".")
        val splitted = cleared.split('.').filter { it.isNotBlank() }
        return when (splitted.size) {
            0 -> s
            1 -> cleared.replace('.', separator).replaceAfter(separator, "")
            2 -> splitted.joinToString(separator.toString())
            else -> splitted[0]
                    .plus(separator)
                    .plus(splitted.subList(1, splitted.size - 1).joinToString(""))
        }
    }
}
0
Leo Droidcoder