I am working on a android app and I have an EditText where user can input numbers. I want to format the number using different currency formats (say ##,##,###) and I want to do it on the fly, ie when user enter each digit(not when enter is pressed). I googled around, and came across TextWatcher which I first found promising, but it turned out to be an absolute pain. I am debugging my code on a HTC Desire phone which only has a soft keyboard.
Now I want to get a callback when user press numbers (0 to 9) , del (backspace) key and enter key. From my testing I found these (atleast on my phone)
1) editText onKeyListener is called when user presses del or enter key. When user presses enter, onKey function is called twice for one enter (which I believe is for ACTION_UP and ACTION_DOWN). When user presses del, onKey is called once (only for ACTION_DOWN) which I dont know why. onKey is never called when user presses any digits(0 to 9) which too I cant understand.
2) TextWatchers 3 callback functions are called (beforeTextChanged, onTextChanged, afterTextChanged) whenever user presses any number (0 to 9) key . So I thought by using TextWatcher and onKeyListener together I can get all callbacks I need.
Now my questions are these..
1) First in my HTC soft keyboard there is a key (a keyboard symbol with a down arrow) and when I click on it keyboard is resigned without giving any callback. I still cant believe android letting user to edit a field and resign without letting program to process (save) the edit. Now my editText is showing one value and my object has another value (I am saving user edits on enter, and handling back press on keyboard by reseting editText value with the value in the object , but I have no answer to this keyboard down key).
2) Second, I want to format the number after user entered the new digit. Say I already have 123 on editText and user entered pressed 4, I want my editText to display 1,234. I get full number on onTextChanged() and afterTextChanged() and I can format the number and put it back to editText in any of these callback. Which one should I use? Which is the best practice?
3) Third one is the most crucial problem. When app start I put the current object value in the editText. Say I put 123 on onResume(), and when user enter a digit (say 4) I want it to be 1234. But on my onTextChanged callback what I am getting is 4123. When I press one more key (say 5) I am getting 45123. So for user inputs editText cursor are pointing to end of the text. But when value is set by hand, editText cursor dont seems to be updating. I believe I have to do something in textWatcher callbacks but I dont know what I should do.
I am posting my code below.
public class AppHome extends AppBaseActivity {
private EditText ed = null;
private NumberFormat amountFormatter = null;
private boolean isUserInput = true;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.app_home_screen);
ed = (EditText)findViewById(R.id.main_amount_textfield);
amountFormatter = new DecimalFormat("##,##,###");
ed.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(event.getAction() == KeyEvent.ACTION_DOWN)
return true;
String strippedAmount = ed.getText().toString().replace(",", "");
if(keyCode == KeyEvent.KEYCODE_DEL){
//delete pressed, strip number of comas and then delete least significant digit.
strippedAmount = strippedAmount.substring(0, strippedAmount.length() - 1);
int amountNumeral = 0;
try{
amountNumeral = Integer.parseInt(strippedAmount);
} catch(NumberFormatException e){
}
myObject.amount = amountNumeral;
isUserInput = false;
setFormattedAmount(amountNumeral,ed.getId());
}else if(keyCode == KeyEvent.KEYCODE_ENTER){
//enter pressed, save edits and resign keyboard
int amountNumeral = 0;
try{
amountNumeral = Integer.parseInt(strippedAmount);
} catch(NumberFormatException e){
}
myObject.amount = amountNumeral;
isUserInput = false;
setFormattedAmount(myObject.amount,ed.getId());
//save edits
save();
//resign keyboard..
InputMethodManager in = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
in.hideSoftInputFromWindow(AppHome.this.getCurrentFocus().getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);
}
return true;
}
});
TextWatcher inputTextWatcher = new TextWatcher() {
public void afterTextChanged(Editable s) {
if(isUserInput == false){
//textWatcher is recursive. When editText value is changed from code textWatcher callback gets called. So this variable acts as a flag which tells whether change is user generated or not..Possibly buggy code..:(
isUserInput = true;
return;
}
String strippedAmount = ed.getText().toString().replace(",", "");
int amountNumeral = 0;
try{
amountNumeral = Integer.parseInt(strippedAmount);
} catch(NumberFormatException e){
}
isUserInput = false;
setFormattedAmount(amountNumeral,ed.getId());
}
public void beforeTextChanged(CharSequence s, int start, int count, int after){
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
ed.addTextChangedListener(inputTextWatcher);
}//end of onCreate...
public void setFormattedAmount(Integer amount, Integer inputBoxId){
double amountValue = 0;
String textString =null;
TextView amountInputBox = (TextView) findViewById(inputBoxId);
amountValue = Double.parseDouble(Integer.toString(amount));
textString = amountFormatter.format(amountValue).toString();
amountInputBox.setText(textString);
}
}
I know it is a big question, but I am working on this same problem for 2 days. I am new to android and still cant believe that there is no easy way to process textEdit data on the fly (I done the same on iphone with ease). Thanks all
EDIT: after using input filter
InputFilter filter = new InputFilter() {
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
String strippedAmount = dest.toString() + source;
strippedAmount = strippedAmount.replace(",", "");
int amountNumeral = 0;
try{
amountNumeral = Integer.parseInt(strippedAmount);
} catch(NumberFormatException e){
}
return amountFormatter.format(amountNumeral).toString();
}
};
ed.setFilters(new InputFilter[]{filter});
When app starts I am putting 1,234 on the editText
myObject.amount = 1234;
ed.setText(amountFormatter.format(myObject.amount).toString());
Then when user clicks the editText, keyboard pops up, and say user enters digit 6
I am getting : 61234 I want : 12346
Well, after much head banging, I found a work around for cursor position problem..I dont know whether it is the correct way, But I got it working..
TextWatcher inputTextWatcher = new TextWatcher() {
public void afterTextChanged(Editable s) {
if(isUserInput == false){
//textWatcher is recursive. When editText value is changed from code textWatcher callback gets called. So this variable acts as a flag which tells whether change is user generated or not..Possibly buggy code..:(
isUserInput = true;
ed.setSelection(ed.getText().length());
return;
}
String strippedAmount = ed.getText().toString().replace(",", "");
int amountNumeral = 0;
try{
amountNumeral = Integer.parseInt(strippedAmount);
} catch(NumberFormatException e){
}
isUserInput = false;
setFormattedAmount(amountNumeral,ed.getId());
}
public void beforeTextChanged(CharSequence s, int start, int count, int after){
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
ed.addTextChangedListener(inputTextWatcher);
ed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int length = ed.getText().length();
ed.setCursorVisible(true);
ed.setSelection(length);
}
});
ed.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(event.getAction() == KeyEvent.ACTION_UP)
return true;
String strippedAmount = ed.getText().toString().replace(",", "");
if(keyCode == KeyEvent.KEYCODE_DEL){
//delete pressed, strip number of comas and then delete least significant digit.
strippedAmount = strippedAmount.substring(0, strippedAmount.length() - 1);
int amountNumeral = 0;
try{
amountNumeral = Integer.parseInt(strippedAmount);
} catch(NumberFormatException e){
}
isUserInput = false;
setFormattedAmount(amountNumeral,ed.getId());
return true;
}else if(keyCode == KeyEvent.KEYCODE_ENTER){
//enter pressed, save edits and resign keyboard
int amountNumeral = 0;
try{
amountNumeral = Integer.parseInt(strippedAmount);
} catch(NumberFormatException e){
}
isUserInput = false;
setFormattedAmount(amountNumeral,ed.getId());
//save edits
//resign keyboard..
InputMethodManager in = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
in.hideSoftInputFromWindow(AppHome.this.getCurrentFocus().getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);
return true;
}
return false;
}
});
What I have done is on onClick() of editText, I forcefully put the cursor at the end of the current EditText text, and I have done the same when user pressed any digit. Hope it helps someone..Thanks for everyone who tried to help.
For Masked input, you can subclass InputFilter
Below is a sample InputFilter subclass, which capitalizes all lower case letters:
/**
* This filter will capitalize all the lower case letters that are added
* through edits.
*/
public static class AllCaps implements InputFilter {
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
for (int i = start; i < end; i++) {
if (Character.isLowerCase(source.charAt(i))) {
char[] v = new char[end - start];
TextUtils.getChars(source, start, end, v, 0);
String s = new String(v).toUpperCase();
if (source instanceof Spanned) {
SpannableString sp = new SpannableString(s);
TextUtils.copySpansFrom((Spanned) source,
start, end, null, sp, 0);
return sp;
} else {
return s;
}
}
}
return null; // keep original
}
}
The above code is taken from Android's implementation of InputFilter
After several hours of working I made a phone input mask. For istance, after entering "123456" it converts it to "+1 (234) 56". After deleting of any symbol from any position a cursor moves to a right position, not to a beginning or ending.
In Activity:
etPhone.addTextChangedListener(new PhoneWatcher(etPhone));
In class:
private class PhoneWatcher implements TextWatcher {
private static final String PHONE_MASK = "+# (###) ###-##-##";
private final char[] PHONE_MASK_ARRAY = PHONE_MASK.toCharArray();
private boolean isInTextChanged;
private boolean isInAfterTextChanged;
private EditText editText;
private int shiftCursor;
private String text;
private int cursor;
public PhoneWatcher(EditText editText) {
super();
this.editText = editText;
isInTextChanged = false;
isInAfterTextChanged = false;
}
@Override
public synchronized void beforeTextChanged(CharSequence s, int start, int count, int after) {
shiftCursor = after - count;
}
@Override
public synchronized void onTextChanged(CharSequence s, int start, int before, int count) {
if (!isInTextChanged) {
isInTextChanged = true;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char symbol = s.charAt(i);
if (symbol >= '0' && symbol <= '9')
sb.append(symbol);
}
String digits = sb.toString();
sb.setLength(0);
int j = 0;
for (int i = 0; i < digits.length(); i++) {
char digit = digits.charAt(i);
while (j < PHONE_MASK_ARRAY.length) {
if (PHONE_MASK_ARRAY[j] == '#') {
sb.append(digit);
j++;
break;
} else {
sb.append(PHONE_MASK_ARRAY[j]);
j++;
}
}
}
cursor = editText.getSelectionStart();
text = sb.toString();
if (shiftCursor > 0) {
if (cursor > text.length())
cursor = text.length();
else {
while (cursor < PHONE_MASK_ARRAY.length && PHONE_MASK_ARRAY[cursor - 1] != '#') {
cursor++;
}
}
} else if (shiftCursor < 0) {
while (cursor > 0 && PHONE_MASK_ARRAY[cursor - 1] != '#') {
cursor--;
}
}
}
}
public synchronized void afterTextChanged(Editable s) {
if (!isInAfterTextChanged) {
isInAfterTextChanged = true;
editText.setText(text);
editText.setSelection(cursor);
isInTextChanged = false;
isInAfterTextChanged = false;
}
}
}
来源:https://stackoverflow.com/questions/5645332/android-edittext-keyboard-textwatcher-problem