¿Mejor forma de dar formato a la entrada de moneda editText?

91

Tengo un editText, el valor inicial es $ 0.00. Cuando presiona 1, cambia a $ 0.01. Presione 4, va a $ 0.14. Presione 8, $ 1.48. Presione la tecla de retroceso, $ 0.14, etc.

Eso funciona, el problema es que, si alguien coloca manualmente el cursor, se producen problemas en el formateo. Si eliminaran el decimal, no volverá. Si colocan el cursor delante del decimal y escriben 2, mostrará $ 02,00 en lugar de $ 2,00. Si intentan eliminar el $, eliminará un dígito, por ejemplo.

Aquí está el código que estoy usando, agradecería cualquier sugerencia.

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());
                }
            }
        }
    });
Roger
fuente
1
Disculpe mi ignorancia, pero ¿es este fragmento de código de uno de los métodos del ciclo de vida de la actividad o está en una clase personalizada que creó? ¿Puede proporcionar una muestra de código más completa, por favor? ¡Gracias!
Argus9
Esto funciona para mí.He
pravin maske

Respuestas:

153

Probé su método, pero falla cuando uso grandes números ... Creé esto:

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

       String cleanString = s.toString().replaceAll("[$,.]", "");
                
       double parsed = Double.parseDouble(cleanString);
       String formatted = NumberFormat.getCurrencyInstance().format((parsed/100));
                    
       current = formatted;
       [your_edittext].setText(formatted);
       [your_edittext].setSelection(formatted.length());
       
       [your_edittext].addTextChangedListener(this);
    }
}

Variante de Kotlin:

private var current: String = ""

         override fun onTextChanged(
            s: CharSequence,
            start: Int,
            before: Int,
            count: Int
        ) {
            if (s.toString() != current) {
                discount_amount_edit_text.removeTextChangedListener(this)

                val cleanString: String = s.replace("""[$,.]""".toRegex(), "")

                val parsed = cleanString.toDouble()
                val formatted = NumberFormat.getCurrencyInstance().format((parsed / 100))

                current = formatted
                discount_amount_edit_text.setText(formatted)
                discount_amount_edit_text.setSelection(formatted.length)

                discount_amount_edit_text.addTextChangedListener(this)
            }
        }
Guilherme Oliveira
fuente
36
Podría ser mejor hacer lo siguiente en lugar de asumir el símbolo del dólar: String replaceable = String.format("[%s,.]", NumberFormat.getCurrencyInstance().getCurrency().getSymbol()); String cleanString = s.toString().replaceAll(replaceable, "");
mierda
6
Hmm, en realidad lo he probado yo mismo ahora, el patrón de expresiones regulares de replaceAll debería verse así, para manejar espacios también: String replaceable = String.format("[%s,.\\s]", NumberFormat.getCurrencyInstance().getCurrency().getSymbol());
craigp
6
¿No se recomienda no realizar cambios en onTextChanged() and rather to do so in afterTextChanged () `
codinguser
3
Estoy interesado en saber por qué el oyente cambiado de texto se elimina y luego se vuelve a agregar cada vez. Para mí, funciona si solo se agrega una vez (y moví los cambios a afterTextChanged)
Daniel Wilson
5
No funciona cuando pones 1 -> 0 -> 0 para obtener 1,00. Es porque llegas al punto en el que 0,1 se cambia a 010 cadena y 010 a double10. 10 / 100 = 0,1no puedes pasarlo.
JakubW
30

Basado en algunas de las respuestas anteriores, creé un MoneyTextWatcher que usaría de la siguiente manera:

priceEditText.addTextChangedListener(new MoneyTextWatcher(priceEditText));

y aquí está la clase:

public class MoneyTextWatcher implements TextWatcher {
    private final WeakReference<EditText> editTextWeakReference;

    public MoneyTextWatcher(EditText editText) {
        editTextWeakReference = new WeakReference<EditText>(editText);
    }

    @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) {
        EditText editText = editTextWeakReference.get();
        if (editText == null) return;
        String s = editable.toString();
        if (s.isEmpty()) return;
        editText.removeTextChangedListener(this);
        String cleanString = s.replaceAll("[$,.]", "");
        BigDecimal parsed = new BigDecimal(cleanString).setScale(2, BigDecimal.ROUND_FLOOR).divide(new BigDecimal(100), BigDecimal.ROUND_FLOOR);
        String formatted = NumberFormat.getCurrencyInstance().format(parsed);
        editText.setText(formatted);
        editText.setSelection(formatted.length());
        editText.addTextChangedListener(this);
    }
}
ToddH
fuente
He estado usando esto por un tiempo, pero recientemente encontré un pequeño problema, si mantiene presionado el botón de eliminar en algunos teclados, se elimina toda la palabra / grupo de texto y las causasjava.lang.NumberFormatException: Bad offset/length
BluGeni
1
¡Funcionó perfectamente para mí! Atención a 'editText.setSelection (formatted.length ());' Debe observarse la instancia de propiedad 'maxLength' de EditText en cuestión. maxLength == 13; formatted.length () == 14; Si 'formatted.length' es mayor que 'maxLength', se produce el siguiente error: IndexOutOfBoundsException: setSpan (14 ... 14) termina la longitud más allá de 13
tks
1
@BluGeni para arreglar eso, simplemente agregue una verificación s.isEmpty antes de eliminar el oyente de cambio de texto if (s.isEmpty ()) return; editText.removeTextChangedListener (esto); También en la línea cleanString, s.toString () es redundante
Mike Baglio Jr.
1
La mejor respuesta que una sola sugerencia es cambiar .replaceAll ("[$ ...) por -> .replaceAll (" [^ \\ d.] "," "); como en otra moneda, tienes otros caracteres además del $, como en mi caso era el R $ (brasileño)
user2582318
1
lo siento, la sugerencia correcta es esta -> .replaceAll("[^0-9]", ""), la anterior tiene un límite de 9.999.999 -_-
user2582318
21

Aquí está mi costumbre CurrencyEditText

import android.content.Context;import android.graphics.Rect;import android.text.Editable;import android.text.InputFilter;import android.text.InputType;import android.text.TextWatcher;
import android.util.AttributeSet;import android.widget.EditText;import java.math.BigDecimal;import java.math.RoundingMode;
import java.text.DecimalFormat;import java.text.DecimalFormatSymbols;
import java.util.Locale;

/**
 * Some note <br/>
 * <li>Always use locale US instead of default to make DecimalFormat work well in all language</li>
 */
public class CurrencyEditText extends android.support.v7.widget.AppCompatEditText {
    private static String prefix = "VND ";
    private static final int MAX_LENGTH = 20;
    private static final int MAX_DECIMAL = 3;
    private CurrencyTextWatcher currencyTextWatcher = new CurrencyTextWatcher(this, prefix);

    public CurrencyEditText(Context context) {
        this(context, null);
    }

    public CurrencyEditText(Context context, AttributeSet attrs) {
        this(context, attrs, android.support.v7.appcompat.R.attr.editTextStyle);
    }

    public CurrencyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
        this.setHint(prefix);
        this.setFilters(new InputFilter[] { new InputFilter.LengthFilter(MAX_LENGTH) });
    }

    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect);
        if (focused) {
            this.addTextChangedListener(currencyTextWatcher);
        } else {
            this.removeTextChangedListener(currencyTextWatcher);
        }
        handleCaseCurrencyEmpty(focused);
    }

    /**
     * When currency empty <br/>
     * + When focus EditText, set the default text = prefix (ex: VND) <br/>
     * + When EditText lose focus, set the default text = "", EditText will display hint (ex:VND)
     */
    private void handleCaseCurrencyEmpty(boolean focused) {
        if (focused) {
            if (getText().toString().isEmpty()) {
                setText(prefix);
            }
        } else {
            if (getText().toString().equals(prefix)) {
                setText("");
            }
        }
    }

    private static class CurrencyTextWatcher implements TextWatcher {
        private final EditText editText;
        private String previousCleanString;
        private String prefix;

        CurrencyTextWatcher(EditText editText, String prefix) {
            this.editText = editText;
            this.prefix = prefix;
        }

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

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

        @Override
        public void afterTextChanged(Editable editable) {
            String str = editable.toString();
            if (str.length() < prefix.length()) {
                editText.setText(prefix);
                editText.setSelection(prefix.length());
                return;
            }
            if (str.equals(prefix)) {
                return;
            }
            // cleanString this the string which not contain prefix and ,
            String cleanString = str.replace(prefix, "").replaceAll("[,]", "");
            // for prevent afterTextChanged recursive call
            if (cleanString.equals(previousCleanString) || cleanString.isEmpty()) {
                return;
            }
            previousCleanString = cleanString;

            String formattedString;
            if (cleanString.contains(".")) {
                formattedString = formatDecimal(cleanString);
            } else {
                formattedString = formatInteger(cleanString);
            }
            editText.removeTextChangedListener(this); // Remove listener
            editText.setText(formattedString);
            handleSelection();
            editText.addTextChangedListener(this); // Add back the listener
        }

        private String formatInteger(String str) {
            BigDecimal parsed = new BigDecimal(str);
            DecimalFormat formatter =
                    new DecimalFormat(prefix + "#,###", new DecimalFormatSymbols(Locale.US));
            return formatter.format(parsed);
        }

        private String formatDecimal(String str) {
            if (str.equals(".")) {
                return prefix + ".";
            }
            BigDecimal parsed = new BigDecimal(str);
            // example pattern VND #,###.00
            DecimalFormat formatter = new DecimalFormat(prefix + "#,###." + getDecimalPattern(str),
                    new DecimalFormatSymbols(Locale.US));
            formatter.setRoundingMode(RoundingMode.DOWN);
            return formatter.format(parsed);
        }

        /**
         * It will return suitable pattern for format decimal
         * For example: 10.2 -> return 0 | 10.23 -> return 00, | 10.235 -> return 000
         */
        private String getDecimalPattern(String str) {
            int decimalCount = str.length() - str.indexOf(".") - 1;
            StringBuilder decimalPattern = new StringBuilder();
            for (int i = 0; i < decimalCount && i < MAX_DECIMAL; i++) {
                decimalPattern.append("0");
            }
            return decimalPattern.toString();
        }

        private void handleSelection() {
            if (editText.getText().length() <= MAX_LENGTH) {
                editText.setSelection(editText.getText().length());
            } else {
                editText.setSelection(MAX_LENGTH);
            }
        }
    }
}

Úselo en XML como

 <...CurrencyEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

Debe editar 2 constantes a continuación para que sean adecuadas para su proyecto

private static String prefix = "VND ";
private static final int MAX_DECIMAL = 3;

ingrese la descripción de la imagen aquí

Demostración en github

Phan Van Linh
fuente
2
¡Esto es brillante!
YTerle
1
Descubrí que después de escribir para alcanzar el número máximo de lugares decimales, intentar ingresar un número 5-9 aumentará el último lugar decimal en 1 ... ¡está redondeando hacia arriba! Mi solución fue llamar formatter.setRoundingMode(RoundingMode.DOWN);al formatDecimalmétodo.
BW
@bwicks muchas gracias por encontrar el problema. He aprobado su edición
Phan Van Linh
¿Cómo poner el símbolo de moneda en lugar de VND?
Mayur Karmur
1
Otra idea de mejora: si el usuario ingresa $., cuando obtenemos el valor bruto como .y lo analizamos en Double, da NFE. Para arreglarlo, hice formatDecimal()volver prefix + "0.";y me cambié #,###.al #,##0.interior formatDecimal(). Esto también se ve mejor cuando el usuario ingresa solo lugares decimales. Se muestra como en $0.25lugar de $.25.
Gokhan Arik
13

En realidad, la solución proporcionada anteriormente no funciona. No funciona si desea ingresar 100,00.

Reemplazar:

double parsed = Double.parseDouble(cleanString);
String formato = NumberFormat.getCurrencyInstance().format((parsed/100));

Con:

BigDecimal parsed = new BigDecimal(cleanString).setScale(2,BigDecimal.ROUND_FLOOR).divide(new BigDecimal(100),BigDecimal.ROUND_FLOOR);                
String formato = NumberFormat.getCurrencyInstance().format(parsed);

Debo decir que hice algunas modificaciones a mi código. La cosa es que deberías estar usando BigDecimal's

sfratini
fuente
6

Cambio la clase con los implementos TextWatcher para usar formatos de moneda de Brasil y ajustar la posición del cursor al editar el valor.

MoneyTextWatcher de clase pública implementa TextWatcher {

    privado EditText editText;

    private String lastAmount = "";

    private int lastCursorPosition = -1;

    public MoneyTextWatcher (EditText editText) {
        súper();
        this.editText = editText;
    }

    @Anular
    public void onTextChanged (cantidad de CharSequence, int start, int before, int count) {

        if (! amount.toString (). equals (lastAmount)) {

            String cleanString = clearCurrencyToNumber (amount.toString ());

            tratar {

                String formattedAmount = transformToCurrency (cleanString);
                editText.removeTextChangedListener (esto);
                editText.setText (formattedAmount);
                editText.setSelection (formattedAmount.length ());
                editText.addTextChangedListener (esto);

                if (lastCursorPosition! = lastAmount.length () && lastCursorPosition! = -1) {
                    int lengthDelta = formattedAmount.length () - lastAmount.length ();
                    int newCursorOffset = max (0, min (formattedAmount.length (), lastCursorPosition + lengthDelta));
                    editText.setSelection (newCursorOffset);
                }
            } captura (Excepción e) {
               // registrar algo
            }
        }
    }

    @Anular
    public void afterTextChanged (Editable s) {
    }

    @Anular
    public void beforeTextChanged (CharSequence s, int start, int count, int after) {
        Valor de cadena = s.toString ();
        if (! value.equals ("")) {
            String cleanString = clearCurrencyToNumber (valor);
            String formattedAmount = transformToCurrency (cleanString);
            lastAmount = formattedAmount;
            lastCursorPosition = editText.getSelectionStart ();
        }
    }

    public static String clearCurrencyToNumber (String currencyValue) {
        Resultado de cadena = nulo;

        if (currencyValue == null) {
            resultado = "";
        } más {
            result = currencyValue.replaceAll ("[(az) | (AZ) | ($ ,.)]", "");
        }
        devolver resultado;
    }

    public static boolean isCurrencyValue (String currencyValue, boolean podeSerZero) {
        resultado booleano;

        if (currencyValue == null || currencyValue.length () == 0) {
            resultado = falso;
        } más {
            if (! podeSerZero && currencyValue.equals ("0,00")) {
                resultado = falso;
            } más {
                resultado = verdadero;
            }
        }
        devolver resultado;
    }

    cadena estática pública transformToCurrency (valor de cadena) {
        analizado doble = Double.parseDouble (valor);
        Cadena formateada = NumberFormat.getCurrencyInstance (new Locale ("pt", "BR")). Format ((parsed / 100));
        formatted = formatted.replaceAll ("[^ (0-9) (.,)]", "");
        retorno formateado;
    }
}
Henrique Ho
fuente
En esta línea "int newCursorOffset = max (0, min (formattedAmount.length (), lastCursorPosition + lengthDelta));" ¿Qué tipo de objeto es máximo y mínimo?
Arthur Melo
2
@ArthurMelo Its, Math.max, Math.min Gracias por el código, y parece fallar al eliminar la coma del texto de edición.
Marcos Vasconcelos
4

Me basé en la respuesta de Guilhermes, pero conservo la posición del cursor y también trato los puntos de manera diferente; de ​​esta manera, si un usuario escribe después del punto, no afecta los números antes del período. Encuentro que esto da una entrada muy suave. .

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


                   // We strip off the currency symbol
                   String replaceable = String.format("[%s,\\s]", NumberFormat.getCurrencyInstance().getCurrency().getSymbol());
                   String cleanString = s.toString().replaceAll(replaceable, "");

                   double price;

                   // Parse the string                     
                   try
                   {
                       price = Double.parseDouble(cleanString);
                   }
                   catch(java.lang.NumberFormatException e)
                   {
                       price = 0;
                   }

                   // If we don't see a decimal, then the user must have deleted it.
                   // In that case, the number must be divided by 100, otherwise 1
                   int shrink = 1;
                   if(!(s.toString().contains(".")))
                   {
                       shrink = 100;
                   }

                   // Reformat the number
                   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)
        {
        }
    });
genixpro
fuente
me ayuda mucho. Gracias @genixpro
Harin Kaklotar
Me gusta tu idea, pero se ve más suave si guardas el número de dígitos después del cursor y luego estableces Selección (longitud - después).
Alpha Huang
¡Muy interesante! El uso de reemplazable funcionó en mi dispositivo físico, pero no funciona en el emulador.
Aliton Oliveira
4

Aunque hay muchas respuestas aquí, me gustaría compartir este código que encontré aquí ya que creo que es la respuesta más sólida y limpia.

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

}

Espero eso ayude.

Kayvan N
fuente
¿No eliminaría eso el punto decimal? Entonces, no podría notar la diferencia entre $ 100.00 y $ 10,000, a menos que me esté perdiendo algo.
nasch
2
esta es la respuesta perfecta! trabajó para mi. vaya, solo piense en cuánto tiempo dediqué a esas respuestas y finalmente me desplacé hacia abajo y encontré la que quería.
Ge Rong
Me alegro de que te haya ayudado.
Kayvan N
@nasch Este es un TextWatcher y formatea el texto como tipos de usuario, lo que evita el caso que menciona.
Kayvan N
@KayvanN Sé lo que es un TextWatcher. replaceAll("\\D", "")eliminará todo lo que no sea un dígito, por lo que "$ 100.00" y "$ 10,000" se convertirán en "10000". Parece que está contando con la entrada para incluir centavos. Entonces, si eso está garantizado, genial, pero si no, creo que habrá problemas.
nasch
4

Ok, aquí hay una mejor manera de lidiar con los formatos de moneda, pulsación de tecla eliminar-retroceder. El código se basa en el código @androidcurious 'anterior ... Pero, trata algunos problemas relacionados con la eliminación hacia atrás y algunas excepciones de análisis: http://miguelt.blogspot.ca/2013/01/textwatcher-for-currency-masksformatting .html [ACTUALIZAR] La solución anterior tenía algunos problemas ... Esta es una mejor solución: http://miguelt.blogspot.ca/2013/02/update-textwatcher-for-currency.html Y ... aquí están los detalles:

Este enfoque es mejor ya que utiliza los mecanismos convencionales de Android. La idea es formatear valores después de que el usuario exista la Vista.

Defina un InputFilter para restringir los valores numéricos; esto es necesario en la mayoría de los casos porque la pantalla no es lo suficientemente grande para acomodar vistas de EditText largas. Esta puede ser una clase interna estática o simplemente otra clase simple:

/** Numeric range Filter. */
class NumericRangeFilter implements InputFilter {
    /** Maximum value. */
    private final double maximum;
    /** Minimum value. */
    private final double minimum;
    /** Creates a new filter between 0.00 and 999,999.99. */
    NumericRangeFilter() {
        this(0.00, 999999.99);
    }
    /** Creates a new filter.
     * @param p_min Minimum value.
     * @param p_max Maximum value. 
     */
    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) {
                // Returning null will make the EditText to accept more values.
                return null;
            }
        } catch (NumberFormatException p_ex) {
            // do nothing
        }
        // Value is out of range - return empty string.
        return "";
    }
}

Defina una clase (estática interna o simplemente una clase) que implementará View.OnFocusChangeListener. Tenga en cuenta que estoy usando una clase de utilidades; la implementación se puede encontrar en "Cantidades, impuestos".

/** Used to format the amount views. */
class AmountOnFocusChangeListener implements View.OnFocusChangeListener {
    @Override
    public void onFocusChange(View p_view, boolean p_hasFocus) {
        // This listener will be attached to any view containing amounts.
        EditText v_amountView = (EditText)p_view;
        if (p_hasFocus) {
            // v_value is using a currency mask - transfor over to cents.
            String v_value = v_amountView.getText().toString();
            int v_cents = Utils.parseAmountToCents(v_value);
            // Now, format cents to an amount (without currency mask)
            v_value = Utils.formatCentsToAmount(v_cents);
            v_amountView.setText(v_value);
            // Select all so the user can overwrite the entire amount in one shot.
            v_amountView.selectAll();
        } else {
            // v_value is not using a currency mask - transfor over to cents.
            String v_value = v_amountView.getText().toString();
            int v_cents = Utils.parseAmountToCents(v_value);
            // Now, format cents to an amount (with currency mask)
            v_value = Utils.formatCentsToCurrency(v_cents);
            v_amountView.setText(v_value);
        }
    }
}

Esta clase eliminará el formato de moneda al editar, basándose en mecanismos estándar. Cuando el usuario sale, se vuelve a aplicar el formato de moneda.

Es mejor definir algunas variables estáticas para minimizar el número de instancias:

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

Finalmente, dentro de onCreateView (...):

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

Puede reutilizar FILTERS y ON_FOCUS en cualquier número de vistas de EditText.

Aquí está la clase Utils:

public class Utils {

   private static final NumberFormat FORMAT_CURRENCY = NumberFormat.getCurrencyInstance();
   /** Parses an amount into cents.
    * @param p_value Amount formatted using the default currency. 
    * @return Value as 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 doesn't have a currency format.
               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;
           }
       }
   }
   /** Formats cents into a valid amount using the default currency.
    * @param p_value Value as cents 
    * @return Amount formatted using a currency.
    */
   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(",", "");
   }
   /** Formats cents into a valid amount using the default currency.
    * @param p_value Value as cents 
    * @return Amount formatted using a currency.
    */
   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());
   }

}
Miguelt
fuente
Si bien, en teoría, esto puede responder a la pregunta, nos gustaría que incluyera las partes esenciales del artículo vinculado en su respuesta y proporcione el enlace como referencia . De no hacerlo, la respuesta corre el riesgo de que el enlace se pudra.
Kev
Recibo java.lang.NumberFormatException: Invalid double: "$ 12,345.00" cuando el texto de edición pierde el foco. Como arreglarlo.
Madhan
4

Usé la implementación a la que hizo referencia Nathan Leigh y las expresiones regulares sugeridas por Kayvan N y user2582318 para eliminar todos los caracteres excepto los dígitos para crear la siguiente versión:

fun EditText.addCurrencyFormatter() {

    // Reference: /programming/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)
                // strip off the currency symbol

                // Reference for this replace regex: /programming/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()
                // format the double into a currency format
                val formated = NumberFormat.getCurrencyInstance()
                        .format(parsed / 100)

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

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

}

Esta es una función de extensión en Kotlin que agrega TextWatcher al TextChangedListener de EditText.

Para usarlo, simplemente:

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

Espero que ayude.

Francisco Junior
fuente
3

Obtuve esto de aquí y lo cambié para cumplir con el formato de moneda portuguesa.

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

}

El layout.xml

<EditText
    android:id="@+id/edit_text_your_id"
    ...
    android:text="0,00 €"
    android:inputType="numberDecimal"
    android:digits="0123456789" />

Haz que funcione

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

Para mi funcion asi

 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() > 2) {
                        Float in=Float.parseFloat(userInput);
                        price = Math.round(in); // just to get an Integer
                        //float percen = in/100;
                        String first, last;
                        first = userInput.substring(0, userInput.length()-2);
                        last = userInput.substring(userInput.length()-2);
                        edEx1.setText("$"+first+"."+last);
                        Log.e(MainActivity.class.toString(), "first: "+first + " last:"+last);
                        edEx1.setSelection(edEx1.getText().length());
                    }
                }
            }
Fernando
fuente
2

Es mejor utilizar la interfaz InputFilter. Es mucho más fácil manejar cualquier tipo de entrada usando expresiones regulares. Mi solución para el formato de entrada de moneda:

public class CurrencyFormatInputFilter implements InputFilter {

Pattern mPattern = Pattern.compile("(0|[1-9]+[0-9]*)(\\.[0-9]{1,2})?");

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

String result = 
        dest.subSequence(0, dstart)
        + source.toString() 
        + dest.subSequence(dend, dest.length());

Matcher matcher = mPattern.matcher(result);

if (!matcher.matches()) return dest.subSequence(dstart, dend);

return null;
}
}

Válido: 0,00, 0,0, 10,00, 111,1 No
válido: 0, 0,000, 111, 10, 010,00, 01,0

Cómo utilizar:

editText.setFilters(new InputFilter[] {new CurrencyFormatInputFilter()});
Mussa
fuente
1

Si su campo de moneda json es de tipo numérico (y no String), puede ser 3.1, 3.15 o solo 3. Porque json redondea automáticamente los campos numéricos.

En este caso, es posible que deba redondearlo para una visualización adecuada (y para poder usar una máscara en un campo de entrada más adelante):

    NumberFormat nf = NumberFormat.getCurrencyInstance();

    float value = 200 // it can be 200, 200.3 or 200.37, BigDecimal will take care
    BigDecimal valueAsBD = BigDecimal.valueOf(value);
    valueAsBD.setScale(2, BigDecimal.ROUND_HALF_UP);

    String formated = nf.format(valueAsBD);

¿Por qué es necesario esto?

Todas las respuestas apuntan a eliminar los símbolos de moneda al escribir, juzgando que está recibiendo los centavos y, por lo tanto, formatear dolar + cents / 100 = dolar, cents. Pero si su campo de moneda json es un tipo de número (y no una Cadena), redondeará sus centavos, puede ser 3, 3.1 o 3.15.

sagits
fuente
1
Exactamente lo que necesitaba. ¡Gracias!
Erick Engelhardt
come as 3.1 , 3.15 or just 3. Because json automatically round number fields- ¡Esto no tiene nada en común con el redondeo !
Marcin Orlowski
1

otro enfoque, pero basado en la respuesta de Guilherme . Este enfoque es útil cuando la configuración regional de su país no está disponible o si desea utilizar símbolos de moneda personalizados. Esta implementación es solo para valores no decimales positivos.

este código está en Kotlin, primer delegado setMaskingMoneydeEditText

fun EditText.setMaskingMoney(currencyText: String) {
    this.addTextChangedListener(object: MyTextWatcher{
        val editTextWeakReference: WeakReference<EditText> = WeakReference<EditText>(this@setMaskingMoney)
        override fun afterTextChanged(editable: Editable?) {
            val editText = editTextWeakReference.get() ?: return
            val s = editable.toString()
            editText.removeTextChangedListener(this)
            val cleanString = s.replace("[Rp,. ]".toRegex(), "")
            val newval = currencyText + cleanString.monetize()

            editText.setText(newval)
            editText.setSelection(newval.length)
            editText.addTextChangedListener(this)
        }
    })
}

Entonces la MyTextWatcherinterfaz debería extenderse desde TextWatcher. Dado que solo necesitamos el afterTextChangedmétodo, los otros métodos deben anularse en esta interfaz

interface MyTextWatcher: TextWatcher {
    override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
    override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
}

y los métodos de monetización son:

fun String.monetize(): String = if (this.isEmpty()) "0"
    else DecimalFormat("#,###").format(this.replace("[^\\d]".toRegex(),"").toLong())

Implementaciones completas:

fun EditText.setMaskingMoney(currencyText: String) {
    this.addTextChangedListener(object: MyTextWatcher{
        val editTextWeakReference: WeakReference<EditText> = WeakReference<EditText>(this@setMaskingMoney)
        override fun afterTextChanged(editable: Editable?) {
            val editText = editTextWeakReference.get() ?: return
            val s = editable.toString()
            editText.removeTextChangedListener(this)
            val cleanString = s.replace("[Rp,. ]".toRegex(), "")
            val newval = currencyText + cleanString.monetize()

            editText.setText(newval)
            editText.setSelection(newval.length)
            editText.addTextChangedListener(this)
        }
    })
}

interface MyTextWatcher: TextWatcher {
    override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
    override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
}


fun String.monetize(): String = if (this.isEmpty()) "0"
    else DecimalFormat("#,###").format(this.replace("[^\\d]".toRegex(),"").toLong())

y en algún lugar del método onCreate:

yourTextView.setMaskingMoney("Rp. ")
Hayi Nukman
fuente
1

Después de mucha búsqueda y falla con Doubles, BigDecimals y así sucesivamente, he creado este código. Funciona plug and play. Está en kotlin. Entonces, para ayudar a otros estancados como yo, vamos.

Básicamente, el código es una función que colocará un textWatcher y ajustará la coma al lugar correcto.

Primero, crea esta función:

fun CurrencyWatcher( editText:EditText) {

    editText.addTextChangedListener(object : TextWatcher {
        //this will prevent the loop
        var changed: Boolean = false

        override fun afterTextChanged(p0: Editable?) {
            changed = false

        }

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

            editText.setSelection(p0.toString().length)
        }

        @SuppressLint("SetTextI18n")
        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
            if (!changed) {
                changed = true

                var str: String = p0.toString().replace(",", "").trim()
                var element0: String = str.elementAt(0).toString()
                var element1: String = "x"
                var element2: String = "x"
                var element3: String = "x"
                var element4: String = "x"
                var element5: String = "x"
                var element6: String = "x"

                //this variables will store each elements of the initials data for the case we need to move this numbers like: 0,01 to 0,11 or 0,11 to 0,01
                if (str.length >= 2) {
                    element1 = str.elementAt(1).toString()
                }
                if (str.length >= 3) {
                    element2 = str.elementAt(2).toString()
                }

                editText.removeTextChangedListener(this)


                //this first block of code will take care of the case
                //where the number starts with 0 and needs to adjusta the 0 and the "," place
                if (str.length == 1) {
                    str = "0,0" + str
                    editText.setText(str)

                } else if (str.length <= 3 && str == "00") {

                    str = "0,00"
                    editText.setText(str)
                    editText.setSelection(str.length)
                } else if (element0 == "0" && element1 == "0" && element2 == "0") {
                    str = str.replace("000", "")
                    str = "0,0" + str
                    editText.setText(str)
                } else if (element0 == "0" && element1 == "0" && element2 != "0") {
                    str = str.replace("00", "")
                    str = "0," + str
                    editText.setText(str)
                } else {

                    //This block of code works with the cases that we need to move the "," only because the value is bigger
                    //lets get the others elements
                    if (str.length >= 4) {
                        element3 = str.elementAt(3).toString()
                    }
                    if (str.length >= 5) {
                        element4 = str.elementAt(4).toString()
                    }
                    if (str.length >= 6) {
                        element5 = str.elementAt(5).toString()
                    }
                    if (str.length == 7) {
                        element6 = str.elementAt(6).toString()
                    }


                    if (str.length >= 4 && element0 != "0") {

                        val sb: StringBuilder = StringBuilder(str)
                        //set the coma in right place
                        sb.insert(str.length - 2, ",")
                        str = sb.toString()
                    }

                    //change the 0,11 to 1,11
                    if (str.length == 4 && element0 == "0") {

                        val sb: StringBuilder = StringBuilder(str)
                        //takes the initial 0 out
                        sb.deleteCharAt(0);
                        str = sb.toString()

                        val sb2: StringBuilder = StringBuilder(str)
                        sb2.insert(str.length - 2, ",")
                        str = sb2.toString()
                    }

                    //this will came up when its like 11,11 and the user delete one, so it will be now 1,11
                    if (str.length == 3 && element0 != "0") {
                        val sb: StringBuilder = StringBuilder(str)
                        sb.insert(str.length - 2, ",")
                        str = sb.toString()
                    }

                    //came up when its like 0,11 and the user delete one, output will be 0,01
                    if (str.length == 2 && element0 == "0") {
                        val sb: StringBuilder = StringBuilder(str)
                        //takes 0 out
                        sb.deleteCharAt(0);
                        str = sb.toString()

                        str = "0,0" + str

                    }

                    //came up when its 1,11 and the user delete, output will be 0,11
                    if (str.length == 2 && element0 != "0") {
                        val sb: StringBuilder = StringBuilder(str)
                        //retira o 0 da frente
                        sb.insert(0, "0,")
                        str = sb.toString()

                    }


                    editText.setText(str)
                }

                //places the selector at the end to increment the number
                editText.setSelection(str.length)
                editText.addTextChangedListener(this)
            }

        }
    })
}

Y luego llamas a esta función de esta manera

val etVal:EditText = findViewById(R.id.etValue)

CurrencyWatcher(etVal)
Thiago Silva
fuente
0

Después de ver la mayor parte de los puestos StackOverflow sobre diferentes maneras de lograr esto usando una TextWatcher, InputFiltero una biblioteca como CurrencyEditText me he decidido por esta sencilla solución usando una OnFocusChangeListener.

La lógica es analizar el EditTexta un número cuando está enfocado y volver a formatearlo cuando pierde el enfoque.

amount.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View view, boolean hasFocus) {
            Number numberAmount = 0f;
            try {
                numberAmount = Float.valueOf(amount.getText().toString());
            } catch (NumberFormatException e1) {
                e1.printStackTrace();
                try {
                    numberAmount = NumberFormat.getCurrencyInstance().parse(amount.getText().toString());
                } catch (ParseException e2) {
                    e2.printStackTrace();
                }
            }
            if (hasFocus) {
                amount.setText(numberAmount.toString());
            } else {
                amount.setText(NumberFormat.getCurrencyInstance().format(numberAmount));
            }
        }
    });
Abtin Gramian
fuente
0

Implementé una versión de Kotlin + Rx.

Es para la moneda brasileña (por ejemplo, 1.500,00 - 5,21 - 192,90) pero puede adaptarse fácilmente a otros formatos.

Espero que alguien más lo encuentre útil.

RxTextView
            .textChangeEvents(fuel_price) // Observe text event changes
            .filter { it.text().isNotEmpty() } // do not accept empty text when event first fires
            .flatMap {
                val onlyNumbers = Regex("\\d+").findAll(it.text()).fold(""){ acc:String,it:MatchResult -> acc.plus(it.value)}
                Observable.just(onlyNumbers)
            }
            .distinctUntilChanged()
            .map { it.trimStart('0') }
            .map { when (it.length) {
                        1-> "00"+it
                        2-> "0"+it
                        else -> it }
            }
            .subscribe {
                val digitList = it.reversed().mapIndexed { i, c ->
                    if ( i == 2 ) "${c},"
                    else if ( i < 2 ) c
                    else if ( (i-2)%3==0 ) "${c}." else c
                }

                val currency = digitList.reversed().fold(""){ acc,it -> acc.toString().plus(it) }
                fuel_price.text = SpannableStringBuilder(currency)
                fuel_price.setSelection(currency.length)
            }
Vinicius Lima
fuente
0

CurrencyTextWatcher.java

public class CurrencyTextWatcher implements TextWatcher {

    private final static String DS = "."; //Decimal Separator
    private final static String TS = ","; //Thousands Separator
    private final static String NUMBERS = "0123456789"; //Numbers
    private final static int MAX_LENGTH = 13; //Maximum Length

    private String format;

    private DecimalFormat decimalFormat;
    private EditText editText;

    public CurrencyTextWatcher(EditText editText) {
        String pattern = "###" + TS + "###" + DS + "##";
        decimalFormat = new DecimalFormat(pattern);
        this.editText = editText;
        this.editText.setInputType(InputType.TYPE_CLASS_NUMBER);
        this.editText.setKeyListener(DigitsKeyListener.getInstance(NUMBERS + DS));
        this.editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(MAX_LENGTH)});
    }

    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void afterTextChanged(Editable editable) {

        editText.removeTextChangedListener(this);
        String value = editable.toString();
        if (!value.isEmpty()) {
            value = value.replace(TS, "");
            try {
                format = decimalFormat.format(Double.parseDouble(value));
                format = format.replace("0", "");
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }

            editText.setText(format);
        }

        editText.addTextChangedListener(this);
    }
}

EditTextCurrency.java

public class EditTextCurrency extends AppCompatEditText {
    public EditTextCurrency(Context context) {
        super(context);
    }

    public EditTextCurrency(Context context, AttributeSet attrs) {
        super(context, attrs);
        addTextChangedListener(new CurrencyTextWatcher(this));
    }
}

ingrese la descripción de la imagen aquí

Samet ÖZTOPRAK
fuente
0

Así es como pude mostrar una moneda en un EditText que fue fácil de implementar y funciona bien para el usuario sin la posibilidad de símbolos locos por todas partes. Esto no intentará aplicar formato hasta que EditText ya no tenga el foco. El usuario puede volver atrás y realizar modificaciones sin poner en peligro el formato. Utilizo la variable 'formattedPrice' solo para mostrar, y la variable 'itemPrice' como el valor que almaceno / uso para los cálculos.

Parece que está funcionando muy bien, pero solo he estado en esto durante algunas semanas, ¡así que cualquier crítica constructiva es absolutamente bienvenida!

La vista EditText en el xml tiene el siguiente atributo:

android:inputType="numberDecimal"

Variables globales:

private String formattedPrice;
private int itemPrice = 0;

En el método onCreate:

EditText itemPriceInput = findViewById(R.id.item_field_price);

itemPriceInput.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        String priceString = itemPriceInput.getText().toString();

        if (! priceString.equals("")) {
            itemPrice = Double.parseDouble(priceString.replaceAll("[$,]", ""));
            formattedPrice = NumberFormat.getCurrencyInstance().format(itemPrice);
            itemPriceInput.setText(formattedPrice);
        }
    }
});
Kat
fuente
0

En caso de que alguien esté interesado en una forma de hacerlo usando RxBinding y Kotlin:

var isEditing = false

RxTextView.textChanges(dollarValue)
            .filter { !isEditing }
            .filter { it.isNotBlank() }
            .map { it.toString().filter { it.isDigit() } }
            .map { BigDecimal(it).setScale(2, BigDecimal.ROUND_FLOOR).divide(100.toBigDecimal(), BigDecimal.ROUND_FLOOR) }
            .map { NumberFormat.getCurrencyInstance(Locale("pt", "BR")).format(it) }
            .subscribe {
                isEditing = true
                dollarValue.text = SpannableStringBuilder(it)
                dollarValue.setSelection(it.length)
                isEditing = false
            }
Guilherme V.
fuente
0

solo un comentario adicional a la respuesta aprobada. Es posible que se bloquee al mover el cursor en el campo edittext debido al análisis. Hice una declaración de captura de prueba, pero implemente su propio código.

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

            String cleanString = s.toString().replaceAll("[$,.]", "");

            try{
                double parsed = Double.parseDouble(cleanString);
                String formatted = NumberFormat.getCurrencyInstance().format((parsed/100));
                current = formatted;
                amountEditText.setText(formatted);
                amountEditText.setSelection(formatted.length());
            } catch (Exception e) {

            }

            amountEditText.addTextChangedListener(this);
        }
    }
Andrew Trang
fuente
0

puedes usar estos métodos

import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
import android.widget.TextView
import java.text.NumberFormat
import java.util.*

fun TextView.currencyFormat() {
    addTextChangedListener(object : TextWatcher {
        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) {
            removeTextChangedListener(this)
            text = if (s?.toString().isNullOrBlank()) {
                ""
            } else {
                s.toString().currencyFormat()
            }
            if(this@currencyFormat is EditText){
                setSelection(text.toString().length)
            }
            addTextChangedListener(this)
        }
    })
}

fun String.currencyFormat(): String {
    var current = this
    if (current.isEmpty()) current = "0"
    return try {
        if (current.contains('.')) {
            NumberFormat.getNumberInstance(Locale.getDefault()).format(current.replace(",", "").toDouble())
        } else {
            NumberFormat.getNumberInstance(Locale.getDefault()).format(current.replace(",", "").toLong())
        }
    } catch (e: Exception) {
        "0"
    }
}
Kourosh
fuente
0

Versión de Kotlin :

    var current = ""

    editText.addTextChangedListener(object: TextWatcher {
        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) {
            val stringText = s.toString()

            if(stringText != current) {
                editText.removeTextChangedListener(this)

                val locale: Locale = Locale.UK
                val currency = Currency.getInstance(locale)
                val cleanString = stringText.replace("[${currency.symbol},.]".toRegex(), "")
                val parsed = cleanString.toDouble()
                val formatted = NumberFormat.getCurrencyInstance(locale).format(parsed / 100)

                current = formatted
                editText.setText(formatted)
                editText.setSelection(formatted.length)
                editText.addTextChangedListener(this)
            }
        }
    })
Adriatik Gashi
fuente
0
public class MoneyEditText extends android.support.v7.widget.AppCompatEditText{
public MoneyEditText(Context context) {
    super(context);
    addTextChangedListener(MoneySplitter());
}
public MoneyEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
    addTextChangedListener(MoneySplitter());
}
public MoneyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    addTextChangedListener(MoneySplitter());
}
public TextWatcher MoneySplitter() {
    TextWatcher textWatcher = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            try
            {
                removeTextChangedListener(this);
                String value = s.toString();
                if (!value.equals(""))
                {
                        if(!TextUtils.isEmpty(value))
                            setText(formatPrice(Double.parseDouble(value)));
                        setSelection(getText().toString().length());

                }
                addTextChangedListener(this);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
                addTextChangedListener(this);
            }
        }
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }
        @Override
        public void afterTextChanged(Editable s) {
        }
    };
    return textWatcher;
}

public static String formatPrice(double value){
        int DecimalPointNumber = 2;
        Locale locale = Locale.getDefault();
        DecimalFormat myFormatter = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
        StringBuilder sb = new StringBuilder();
        if(DecimalPointNumber>0){
            for (int i = 0; i < DecimalPointNumber; i++) {
                sb.append("#");
            }
            myFormatter.applyPattern("###,###."+ sb.toString());
        }else
            myFormatter.applyPattern("###,###"+ sb.toString());

            return Currency.getInstance(Locale.getDefault()).getSymbol() + myFormatter.format(value);
    }
}

y luego use este bloque como su editText

   <MoneyEditText
   android:id="@+id/txtPrice"
   android:layout_width="match_parent"
   android:layout_height="64dp"
   android:digits="0123456789.,"
   android:inputType="numberDecimal"
   android:selectAllOnFocus="true"
   android:singleLine="true" />
Saeid Mohammadi
fuente
Puede utilizar este texto de edición personalizado para formatear el texto de entrada como desee.
Saeid Mohammadi
Cambié esta clase para aceptar números negativos. El código está abajo como respuesta.
Michel Fernandes
0

Esto es como la respuesta de Saeid Mohammadi, pero cambié para aceptar números negativos.

  package com.example.liberdade.util
    
    import android.text.Editable
    import android.text.TextWatcher
    import android.widget.EditText
    import java.lang.ref.WeakReference
    import java.math.BigDecimal
    import java.text.NumberFormat
    import java.util.*
    
    
    class MoneyTextWatcher : TextWatcher {
    
    
    
        private val editTextWeakReference: WeakReference<EditText?>?
        private val locale: Locale = Locale("pt", "BR")
        //private final Locale locale;
    
        constructor(editText: EditText?, locale: Locale?) {
            editTextWeakReference = WeakReference<EditText?>(editText)
            //this.locale = if (locale != null) locale else Locale.getDefault()
        }
    
        constructor(editText: EditText?) {
            editTextWeakReference = WeakReference<EditText?>(editText)
            //locale = Locale.getDefault()
        }
    
        override fun beforeTextChanged(
            s: CharSequence?,
            start: Int,
            count: Int,
            after: Int
        ) {
        }
    
        override fun onTextChanged(
            s: CharSequence?,
            start: Int,
            before: Int,
            count: Int
        ) {
        }
    
        override fun afterTextChanged(editable: Editable?) {
            val editText: EditText = editTextWeakReference?.get() ?: return
            editText.removeTextChangedListener(this)
    
            var isNegative = false
            var editableString = editable.toString()
            if (editable != null) {
                if (editableString.contains('-')) {
                    isNegative = true
                    if (editable != null) {
                        editableString = editableString.replace("-","")
                    }
                }
            }
    
            val parsed: BigDecimal? = parseToBigDecimal(editableString, locale)
            //val parsed: BigDecimal? = parseToBigDecimal(editable.toString(), locale)
            var formatted: String = NumberFormat.getCurrencyInstance(locale).format(parsed)
    
            if (isNegative && !(formatted.equals("R\$ 0,00") || formatted.equals("-R\$ 0,00"))) formatted = "-${formatted}"
            editText.setText(formatted)
            editText.setSelection(formatted.length)
            editText.addTextChangedListener(this)
        }
    
        private fun parseToBigDecimal(value: String?, locale: Locale?): BigDecimal? {
            val replaceable = java.lang.String.format(
                "[%s,.\\s]",
                NumberFormat.getCurrencyInstance(locale).currency.symbol
            )
            val cleanString = value!!.replace(replaceable.toRegex(), "")
            return BigDecimal(cleanString).setScale(
                2, BigDecimal.ROUND_FLOOR
            ).divide(
                BigDecimal(100), BigDecimal.ROUND_FLOOR
            )
        }
    }
    
    //como invocar
    //binding.editTextValorCaixa.addTextChangedListener(MoneyTextWatcher(binding.editTextValorCaixa, Locale("pt", "BR")))
Michel Fernandes
fuente