I have a text field where the start symbol is $ (could be euro or pound depending on an application setting). I need to make it so that if the user clicks before the symbol nothing will happen. In other words, the selection must remain after the symbol. I tried doing something like this but it seems wrong and it gave me an error:
billAmount.addTextChangedListener(new TextWatcher() {
//other methods
@Override
public void afterTextChanged(Editable s) {
billAmount.setText(currencySymbol + billAmount.getText().toString());
}
});
I was thinking of using an inputFilter but nothing I tried worked. I'm also not allowed to use a TextView right before the EditText.
First, in your code sample, the reason you are getting an error is because, as others have said, you are calling the setText
method inside the afterTextChanged
method. Calling setText
is obviously changing the text which causes afterTextChanged
to be called again. This results in the afterTextChanged
being called continuously until there is eventually a stack overflow.
You have two issues: 1) You want to always keep the cursor positioned after the currency symbol, and 2) You want to make sure the currency symbol is never somehow removed.
The easiest way to solve #1 is to create a subclass of EditText
and override the onSelectionChanged
method.
public class MyEditText extends EditText {
// ...
@Override
public void onSelectionChanged(int selStart, int selEnd) {
super.onSelectionChanged(selStart, selEnd);
// Make sure the text's length is greater than zero.
// Then, if the cursor is at position zero...
if (getText().length() > 0 && selStart == 0) {
// ...move it over to position one.
setSelection(1, selEnd);
}
}
}
This will force the cursor to always go after the currency symbol even if the user attempts to move it before it. The check getText().length() > 0
is to ensure that the EditText
contains at least one character, otherwise attempting to move the cursor will result in an Exception.
As for #2, there are a couple ways to go at it. You can attempt to use some instance variables inside your TextWatcher to keep track of when the text needs to be formatted, but that won't prevent the unnecessary method calls from actually happening, and it adds some unneeded complexity. I think it would be easier to simply use an InputFilter
, which you can specify in your extended EditText's constructor.
public class MyEditText extends EditText {
public MyEditText(Context context) {
super(context);
// Set the EditText's input filter.
setFilters(new InputFilter[] { new InputFilter {
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
// If the currency symbol is about to be replaced...
if (dstart == 0)
// Add the currency symbol to the front of the source.
return currencySymbol + source;
// else
// Return null to indicate that the change is okay.
return null;
}
}});
}
// ...
}
In the filter
method, the dest
parameter represents the EditText
's text, and the dstart
and dend
parameters represent the start and end positions of the portion of the text that is about to be replaced. Since the currency symbol should always be the first character, we know it is about to be replaced if dstart
is zero, in which case we simply return the source
(which represents the replacement text) with the currency symbol placed in front. Otherwise, we indicate that the change is okay by returning null
.
I tested it, and it seems to work for what you need.
On a side note, although I understand that you're not "allowed" to use a TextView
, I think it's worth reiterating that using one would provide a much better solution to this problem. One particularly useful solution being to have a hidden EditText
contain the raw input from the user, and having the TextView
on top of the EditText
. You would use a TextWatcher
to update the TextView
with the properly formatted input from the EditText
.