Reputation: 4259
I have an editText, starting value is $0.00. When you press 1, it changes to $0.01. Press 4, it goes to $0.14. Press 8, $1.48. Press backspace, $0.14, etc.
That works, the problem is, if somebody manually positions the cursor, problems occur in the formatting. If they were to delete the decimal, it won't come back. If they put the cursor in front of the decimal and type 2, it will display $02.00 instead of $2.00. If they try to delete the $ it will delete a digit instead, for example.
Here is code I'm using, I'd appreciate any 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());
}
}
}
});
Upvotes: 104
Views: 156259
Reputation: 1941
I think this library is better, because :
Upvotes: 0
Reputation: 6461
Use this inputType in your XML
android:inputType="numberSigned|numberDecimal"
Add this nice Kotlin Extension function:
**
* Use this function from [TextWatcher.afterTextChanged] it will first call [AppCompatEditText.removeTextChangedListener]
* on the TextWatcher you pass, manipulate the text and then register it again after it call setText.
*
* @param fallback The String that we will return if the user is doing illegal adding, like trying to add a third digit after the comma.
* It is best if you will keep the fallback as a member of the class the EditText resides in - and store in it
* @param textWatcher [TextWatcher] It will be used to unregister before manipulating the text.
* @param locale The locale that we will pass to [NumberFormat.getCurrencyInstance] - it will affect the currency sign. default is [Locale.US]
*
* @return A formatted String to use in [AppCompatEditText.setText]
*
*/
fun AppCompatEditText.formatCurrency(@NonNull fallback: String, @NonNull textWatcher: TextWatcher,
locale: Locale = Locale.US) {
removeTextChangedListener(textWatcher)
var original = text.toString()
if (original.startsWith(".")) {
// If the user press on '.-' key on the beginning of the amount - we are getting '.' and we turn it into '-'
setText(original.replaceFirst(".", "-"))
addTextChangedListener(textWatcher)
setSelection(text?.length ?: 0)
return
}
val split = original.split(".")
when (split.size) {
0 -> {
setText(fallback)
addTextChangedListener(textWatcher)
setSelection(text?.length ?: 0)
return
}
1 -> {
if (split[0] == "-") {
setText("-")
addTextChangedListener(textWatcher)
setSelection(text?.length ?: 0)
return
}
}
2 -> {
if (split[1].length > 2) {
setText(fallback)
addTextChangedListener(textWatcher)
setSelection(text?.length ?: 0)
return
}
}
}
// We store the decimal value in a local variable
val decimalSplit = original.split(".")
// flag to indicate that we have a decimal part on the original String.
val hasDecimal = decimalSplit.size > 1
if (hasDecimal) {
original = decimalSplit[0]
}
val isNegative = original.startsWith("-")
val cleanString: String = original.replace("""[$,]""".toRegex(), "")
val result = if (cleanString.isNotEmpty() && cleanString != "-") {
val formatString = original.replace("""[-$,.]""".toRegex(), "")
// Add Commas and Currency symbol.
var result = NumberFormat.getCurrencyInstance(locale).format(formatString.toDouble())
result = result.split('.')[0]
if (isNegative) {
// If it was negative we must add the minus sign.
result = "-${result}"
}
if (hasDecimal) {
// after the formatting the decimal is omitted, we need to append it.
result = "${result}.${decimalSplit[1]}"
}
result
} else {
original
}
setText(result)
addTextChangedListener(textWatcher)
setSelection(text?.length ?: 0)
}
And use it like this:
class MyCoolClass{
private var mLastAmount = ""
...
...
private fun addTextWatcherToEt() {
mEtAmount.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
mEtAmount.formatCurrency(mLastAmount, this)
mLastAmount = mEtAmount.text.toString()
// Do More stuff here if you need...
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
}
}
Upvotes: 1
Reputation: 1201
Another approach, but based on Guilherme answer. This approach is useful when your country locale is not available or if you want to use custom currency symbols. This implementation is only for positive non-decimal.
this code is in Kotlin, first, delegate setMaskingMoney
for EditText
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)
}
})
}
Then MyTextWatcher
interface should be extended from TextWatcher
. Since we only need the afterTextChanged
method, the other methods need to override in this interface.
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) {}
}
and the monetize methods is:
fun String.monetize(): String = if (this.isEmpty()) "0"
else DecimalFormat("#,###").format(this.replace("[^\\d]".toRegex(),"").toLong())
Full implementations:
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())
and somewhere on onCreate method:
yourTextView.setMaskingMoney("Rp. ")
Upvotes: 1
Reputation: 2142
I tested your method, but it fails when I use great numbers... I created this:
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);
}
}
Kotlin variant:
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)
}
}
Upvotes: 173
Reputation: 1275
This is like Saeid Mohammadi answer but I changed to accept negative numbers.
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")))
Upvotes: 0
Reputation: 449
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);
}
}
and then use this block as your 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" />
Upvotes: 0
Reputation: 362
Kotlin version:
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)
}
}
})
Upvotes: 0
Reputation: 796
After too much search and fails with Doubles, BigDecimals and so on, I have made this code. It works plug And Play. Its in kotlin. So, to help others stucked like me, lets go.
The code basically is a function that will place a textWatcher and adjust the coma to the right place.
First, create this function:
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)
}
}
})
}
And then you call this function this way
val etVal:EditText = findViewById(R.id.etValue)
CurrencyWatcher(etVal)
Upvotes: 1
Reputation: 2289
you can use these methods
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"
}
}
Upvotes: 0
Reputation: 41
just an additional comment to the approved answer. You may get a crash when moving the cursor on edittext field due to parsing. I did a try catch statement, but implement your own code.
@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);
}
}
Upvotes: 0
Reputation: 2016
I used the implementation Nathan Leigh referenced and Kayvan N's and user2582318's suggested regex to remove all chars except digits to create the following version:
fun EditText.addCurrencyFormatter() {
// Reference: 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) {
[email protected](this)
// strip off the currency symbol
// Reference for this replace regex: 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()
// format the double into a currency format
val formated = NumberFormat.getCurrencyInstance()
.format(parsed / 100)
current = formated
[email protected](formated)
[email protected](formated.length)
[email protected](this)
}
}
})
}
This is an extension function in Kotlin which adds the TextWatcher to the TextChangedListener of the EditText.
In order to use it, just:
yourEditText = (EditText) findViewById(R.id.edit_text_your_id);
yourEditText.addCurrencyFormatter()
I hope it helps.
Upvotes: 7
Reputation: 384
Ok, here is a better way to deal with Currency formats, delete-backward keystroke. The code is based on @androidcurious' code above... But, deals with some problems related to backwards-deletion and some parse exceptions: http://miguelt.blogspot.ca/2013/01/textwatcher-for-currency-masksformatting.html [UPDATE] The previous solution had some problems... This is a better solutoin: http://miguelt.blogspot.ca/2013/02/update-textwatcher-for-currency.html And... here are the details:
This approach is better since it uses the conventional Android mechanisms. The idea is to format values after the user exists the View.
Define an InputFilter to restrict the numeric values – this is required in most cases because the screen is not large enough to accommodate long EditText views. This can be a static inner class or just another plain class:
/** 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 "";
}
}
Define a class (inner static or just a class) that will implement View.OnFocusChangeListener. Note that I'm using an Utils class - the implementation can be found at "Amounts, Taxes".
/** 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);
}
}
}
This class will remove the currency format when editing - relying on standard mechanisms. When the user exits, the currency format is re-applied.
It's better to define some static variables to minimize the number of instances:
static final InputFilter[] FILTERS = new InputFilter[] {new NumericRangeFilter()};
static final View.OnFocusChangeListener ON_FOCUS = new AmountOnFocusChangeListener();
Finally, within the onCreateView(...):
EditText mAmountView = ....
mAmountView.setFilters(FILTERS);
mAmountView.setOnFocusChangeListener(ON_FOCUS);
You can reuse FILTERS and ON_FOCUS on any number of EditText views.
Here is the Utils class:
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());
}
}
Upvotes: 4
Reputation: 1441
In case someone is interested in a way of doing it using RxBinding and 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
}
Upvotes: 0
Reputation: 1
Here is how I was able to display a currency in an EditText that was easy to implement and works well for the user without the potential for crazy symbols all over the place. This will not try to do any formatting until the EditText no longer has focus. The user can still go back and make any edits without jeopardizing the formatting. I use the 'formattedPrice' variable for display only, and the 'itemPrice' variable as the value that I store/use for calculations.
It seems to be working really well, but I've only been at this for a few weeks, so any constructive criticism is absolutely welcome!
The EditText view in the xml has the following attribute:
android:inputType="numberDecimal"
Global variables:
private String formattedPrice;
private int itemPrice = 0;
In the onCreate method:
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);
}
}
});
Upvotes: 0
Reputation: 60923
Here is my custom 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);
}
}
}
}
Use it in XML like
<...CurrencyEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
You should edit 2 constant below for suitable for your project
private static String prefix = "VND ";
private static final int MAX_DECIMAL = 3;
Upvotes: 27
Reputation: 2811
Based on some of the above answers I created a MoneyTextWatcher which you'd use as follows:
priceEditText.addTextChangedListener(new MoneyTextWatcher(priceEditText));
and here's the class:
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);
}
}
Upvotes: 37
Reputation: 1631
I've implemented a Kotlin + Rx version.
It's for brazilian's currency (e.g. 1,500.00 - 5,21 - 192,90) but you can easily adapt for other formats.
Hope someone else finds it helpful.
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)
}
Upvotes: 0
Reputation: 1780
After looking at most of the StackOverflow posts on different ways to achieve this using a TextWatcher
, InputFilter
, or library like CurrencyEditText I've settled on this simple solution using an OnFocusChangeListener
.
The logic is to parse the EditText
to a number when it is focused and to format it back when it loses focus.
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));
}
}
});
Upvotes: 1
Reputation: 101
I change the class with implements TextWatcher to use Brasil currency formats and adjusting cursor position when editing the value.
public class MoneyTextWatcher implements TextWatcher { private EditText editText; private String lastAmount = ""; private int lastCursorPosition = -1; public MoneyTextWatcher(EditText editText) { super(); this.editText = editText; } @Override public void onTextChanged(CharSequence amount, int start, int before, int count) { if (!amount.toString().equals(lastAmount)) { String cleanString = clearCurrencyToNumber(amount.toString()); try { String formattedAmount = transformToCurrency(cleanString); editText.removeTextChangedListener(this); editText.setText(formattedAmount); editText.setSelection(formattedAmount.length()); editText.addTextChangedListener(this); if (lastCursorPosition != lastAmount.length() && lastCursorPosition != -1) { int lengthDelta = formattedAmount.length() - lastAmount.length(); int newCursorOffset = max(0, min(formattedAmount.length(), lastCursorPosition + lengthDelta)); editText.setSelection(newCursorOffset); } } catch (Exception e) { //log something } } } @Override public void afterTextChanged(Editable s) { } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { String value = s.toString(); if(!value.equals("")){ String cleanString = clearCurrencyToNumber(value); String formattedAmount = transformToCurrency(cleanString); lastAmount = formattedAmount; lastCursorPosition = editText.getSelectionStart(); } } public static String clearCurrencyToNumber(String currencyValue) { String result = null; if (currencyValue == null) { result = ""; } else { result = currencyValue.replaceAll("[(a-z)|(A-Z)|($,. )]", ""); } return result; } public static boolean isCurrencyValue(String currencyValue, boolean podeSerZero) { boolean result; if (currencyValue == null || currencyValue.length() == 0) { result = false; } else { if (!podeSerZero && currencyValue.equals("0,00")) { result = false; } else { result = true; } } return result; } public static String transformToCurrency(String value) { double parsed = Double.parseDouble(value); String formatted = NumberFormat.getCurrencyInstance(new Locale("pt", "BR")).format((parsed / 100)); formatted = formatted.replaceAll("[^(0-9)(.,)]", ""); return formatted; } }
Upvotes: 8
Reputation: 6054
If your json currency field is of number type (and not String) it may come as 3.1 , 3.15 or just 3. Because json automatically round number fields.
In this case you may need to round it for proper display (and to be able to use a mask on a input field later):
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);
Why this is needed?
All answers point to removing currency simbols when typing judcing you are receiving the cents and so formating dolar + cents / 100 = dolar,cents. But if your json currency field is an number type (and not an String) it will round your cents, it may be 3 , 3.1 or 3.15.
Upvotes: 1
Reputation: 31
I got this from here and changed it to comply with Portuguese currency format.
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);
}
}
}
The layout.xml
<EditText
android:id="@+id/edit_text_your_id"
...
android:text="0,00 €"
android:inputType="numberDecimal"
android:digits="0123456789" />
Get it to work
yourEditText = (EditText) findViewById(R.id.edit_text_your_id);
yourEditText.setRawInputType(Configuration.KEYBOARD_12KEY);
yourEditText.addTextChangedListener(new CurrencyTextWatcher(yourEditText));
Upvotes: 3
Reputation: 11
I used this to allow the user to enter the currency and to convert it from string into int to store in db and to change back from int into string again
Upvotes: 1
Reputation: 8396
Even though there are lots of answers here, I would like to share this code that I found in here since I believe its the most robust and clean answer.
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) { }
}
hope it helps.
Upvotes: 5
Reputation: 543
I built on Guilhermes answer, but I preserve the position of the cursor and also treat the periods differently - this way if a user is typing after the period, it does not affect the numbers before the period I find that this gives a very smooth input.
[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)
{
}
});
Upvotes: 4
Reputation: 1499
It is better to use InputFilter interface. Much easier to handle any kind of inputs by using regex. My solution for currency input format:
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;
}
}
Valid: 0.00, 0.0, 10.00, 111.1
Invalid: 0, 0.000, 111, 10, 010.00, 01.0
How to use:
editText.setFilters(new InputFilter[] {new CurrencyFormatInputFilter()});
Upvotes: 2
Reputation: 21
For me it worked like this
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());
}
}
}
Upvotes: 2
Reputation: 9978
Actually, the solution provided before is not working. It doesn't work if you want to enter 100.00.
Replace:
double parsed = Double.parseDouble(cleanString);
String formato = NumberFormat.getCurrencyInstance().format((parsed/100));
With:
BigDecimal parsed = new BigDecimal(cleanString).setScale(2,BigDecimal.ROUND_FLOOR).divide(new BigDecimal(100),BigDecimal.ROUND_FLOOR);
String formato = NumberFormat.getCurrencyInstance().format(parsed);
I must say that I made some modifications for my code. The thing is that you should be using BigDecimal's
Upvotes: 14