android EditText ,keyboard textWatcher problem - android

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

Related

Force keyboard to enter certain digit

Hey guys I'm developing an application for Market Transactions and stuff, and the client wants to have a condition on the edit text of type number decimal.
He wants the user to enter only number 5 after the dot if the number is a decimal and only one digit after the dot.
i.e.
15
14
1949
12.33 (not accepted)
12.5 (accepted)
so how to do so please give me some hints
try this and check whether it works.
final String regex = "^\\-?(\\d{0,5}|\\d{0,5}\\.[5]{0,1})$";
((EditText)rootView.findViewById(R.id.editText1)).setFilters(new InputFilter[] {
new InputFilter() {
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned destination, int destinationStart, int destinationEnd) {
if (end > start) {
// adding: filter
// build the resulting text
String destinationString = destination.toString();
String resultingTxt = destinationString.substring(0, destinationStart) + source.subSequence(start, end) + destinationString.substring(destinationEnd);
// return null to accept the input or empty to reject it
return resultingTxt.matches(regex) ? null : "";
}
// removing: always accept
return null;
}
}
});
Here user input is checked to match the regular expression and return the input string if it matches and null if not.
<Edittext
android:id="#+id/testing"
android:inputType="numberDecimal"
android:digits="0123456789."
>
</Edittext>
Then, use InputFilter
testing.setFilters(new InputFilter[]{NumberFilter(), new InputFilter.LengthFilter(6)});
//OR
testing.setFilters(new InputFilter[]{NumberFilter()});
Create class for NumberFilter implemenets InputFilter
public class NumberFilter implements InputFilter{
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
// ignore empty space and not number
if (!isNullorEmpty(source.toString()) && !isOnlyNumber(source.toString()))
return "";
//ignore first .
if (!source.toString().charAt(0).equals("."))
return "";
if ((source.toString().charAt(0) != '8' || source.toString().charAt(0) != '9'))
return "";
return null;
}
}
// to allow 1 and 1.2
public static boolean isOnlyNumber(String number) {
return number.matches("(?<=^| )\d+(\.\d+)?(?=$| )");
}
//check empty space or Null
public static boolean isNullorEmpty(String string){
return StringUtils.isEmpty(string);
}
Try this addTextChangedListener.
You need to append 5 to the editText whenever user inputs a dot.
`flag =0;
_field.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
public void onTextChanged(CharSequence s, int start,
int before, int count) {
if (s.toString().contains(".") && (flag == 0)) {
flag = 1;
_field.setText(_field.getText() + "5");
} else if (s.toString().contains(".") && (flag == 1)) {
} else {
flag = 0;
}
}
});`

Motorola Bionic - input type textPersonName and textCapWords not working

I can't get an EditText field in my app to actually work with inputType="textCapWords" or "textPersonName" on a Droid Bionic. Other phones I've tested seem to be fine. None of the capitalization inputTypes seem to work. I've also tried the deprecated capitalization attribute. And setting things programmatically.
Has anyone else run into this problem? Any suggestions aside from implementing custom functionality using TextWatcher?
<EditText
android:id="#+id/last_name_editor"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="#string/last_name"
android:inputType="textCapWords"/>
Can you check your Device keyboard setting .In some device(Samsung Note 2) they give auto Capitalization check /UN-check option.
Here is the TextWatcher way, for anyone who is still facing this problem:
TextWatcher capitalizeTW = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
try{
String inputString = ""+YOUR_TEXT_VIEW.getText().toString();
String firstLetterCapString = WordUtils.capitalize(inputString);
if(!firstLetterCapString.equals(""+YOUR_TEXT_VIEW.getText().toString())){
YOUR_TEXT_VIEW.setText(""+firstLetterCapString);
YOUR_TEXT_VIEW.setSelection(YOUR_TEXT_VIEW.getText().length());
}
}catch (Exception e){
e.printStackTrace();
}
}
#Override
public void afterTextChanged(Editable editable) {
}
};
//Adding TextWatcher to Your TextView
YOUR_TEXT_VIEW.addTextChangedListener(capitalizeTW);
//WordUtils
public final class WordUtils {
private WordUtils(){
}
public static String capitalize(String str) {
return capitalize(str, (char[]) null);
}
public static String capitalize(String str, char... delimiters) {
int delimLen = delimiters == null ? -1 : delimiters.length;
if (!TextUtils.isEmpty(str) && delimLen != 0) {
char[] buffer = str.toCharArray();
boolean capitalizeNext = true;
for (int i = 0; i < buffer.length; ++i) {
char ch = buffer[i];
if (isDelimiter(ch, delimiters)) {
capitalizeNext = true;
} else if (capitalizeNext) {
buffer[i] = Character.toTitleCase(ch);
capitalizeNext = false;
}
}
return new String(buffer);
} else {
return str;
}
}
private static boolean isDelimiter(char ch, char[] delimiters) {
if (delimiters == null) {
return Character.isWhitespace(ch);
} else {
char[] arr$ = delimiters;
int len$ = delimiters.length;
for (int i$ = 0; i$ < len$; ++i$) {
char delimiter = arr$[i$];
if (ch == delimiter) {
return true;
}
}
return false;
}
}
}
I hope this helps.
Can try this ... worked for me ...
android:inputType="textCapWords"
Or
android:inputType="textCapSentences"
Or
android:inputType="textCapWords"
For Samsung S5/S7 there is additional settings in settings named "Auto Capitalize" for making first charterer in Caps.
If the settings is off it need to selected to type first character in Caps
Either this is a problem specific to me, or nobody seems to have a solution. Went with a TextWatcher solution. Will change this if anybody else comes up with a solution.

EditText and InputFilter cause repeating text

I'm trying to implement an EditText that limits input to alpha chars only [A-Za-z].
I started with the InputFilter method from this post. When I type "a%" the text disappears then if I hit backspace the text is "a". I've tried other variations on the filter function like using a regex to match only [A-Za-z] and sometimes see crazy behavior like repeating chars, I'll type "a" then "b" and get "aab" then type "c" and get "aabaabc" then hit backspace and get "aabaabcaabaabc"!
Here's the code I'm working with so far with the different approaches I've tried.
EditText input = (EditText)findViewById( R.id.inputText );
InputFilter filter = new InputFilter() {
#Override
public CharSequence filter( CharSequence source, int start, int end, Spanned dest, int dstart, int dend ) {
//String data = source.toString();
//String ret = null;
/*
boolean isValid = data.matches( "[A-Za-z]" );
if( isValid ) {
ret = null;
}
else {
ret = data.replaceAll( "[##$%^&*]", "" );
}
*/
/*
dest = new SpannableStringBuilder();
ret = data.replaceAll( "[##$%^&*]", "" );
return ret;
*/
for( int i = start; i < end; i++ ) {
if( !Character.isLetter( source.charAt( i ) ) ) {
return "";
}
}
return null;
}
};
input.setFilters( new InputFilter[]{ filter } );
I'm totally stumped on this one so any help here would be greatly appreciated.
EDIT:
Ok, I've done quite a lot of experimenting with InputFilter and have drawn some conclusions, albeit no solution to the problem. See the comments in my code below. I'm going to try Imran Rana's solution now.
EditText input = (EditText)findViewById( R.id.inputText );
InputFilter filter = new InputFilter() {
// It is not clear what this function should return!
// Docs say return null to allow the new char(s) and return "" to disallow
// but the behavior when returning "" is inconsistent.
//
// The source parameter is a SpannableStringBuilder if 1 char is entered but it
// equals the whole string from the EditText.
// If more than one char is entered (as is the case with some keyboards that auto insert
// a space after certain chars) then the source param is a CharSequence and equals only
// the new chars.
#Override
public CharSequence filter( CharSequence source, int start, int end, Spanned dest, int dstart, int dend ) {
String data = source.toString().substring( start, end );
String retData = null;
boolean isValid = data.matches( "[A-Za-z]+" );
if( !isValid ) {
if( source instanceof SpannableStringBuilder ) {
// This works until the next char is evaluated then you get repeats
// (Enter "a" then "^" gives "a". Then enter "b" gives "aab")
retData = data.replaceAll( "[##$%^&*']", "" );
// If I instead always returns an empty string here then the EditText is blanked.
// (Enter "a" then "^" gives "")
//retData = "";
}
else { // source is instanceof CharSequence
// We only get here if more than 1 char was entered (like "& ").
// And again, this works until the next char is evaluated then you get repeats
// (Enter "a" then "& " gives "a". Then enter "b" gives "aab")
retData = "";
}
}
return retData;
}
};
input.setFilters( new InputFilter[]{ filter } );
Use the following code:
EditText input = (EditText) findViewById(R.id.inputText);
input.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub
for( int i = start;i<s.toString().length(); i++ ) {
if( !Character.isLetter(s.charAt( i ) ) ) {
input.setText("");
}
}
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
If you want the valid text to remain in the EditText:
input.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
for( int i = 0;i<s.toString().length(); i++ ) {
if( !Character.isLetter(s.charAt( i ) ) ) {
s.replace(i, i+1,"");
}
}
}
});
Fix for repeating text, work on all Android Versions:
public static InputFilter getOnlyCharactersFilter() {
return getCustomInputFilter(true, false, false);
}
public static InputFilter getCharactersAndDigitsFilter() {
return getCustomInputFilter(true, true, false);
}
public static InputFilter getCustomInputFilter(final boolean allowCharacters, final boolean allowDigits, final boolean allowSpaceChar) {
return new InputFilter() {
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
boolean keepOriginal = true;
StringBuilder sb = new StringBuilder(end - start);
for (int i = start; i < end; i++) {
char c = source.charAt(i);
if (isCharAllowed(c)) {
sb.append(c);
} else {
keepOriginal = false;
}
}
if (keepOriginal) {
return null;
} else {
if (source instanceof Spanned) {
SpannableString sp = new SpannableString(sb);
TextUtils.copySpansFrom((Spanned) source, start, sb.length(), null, sp, 0);
return sp;
} else {
return sb;
}
}
}
private boolean isCharAllowed(char c) {
if (Character.isLetter(c) && allowCharacters) {
return true;
}
if (Character.isDigit(c) && allowDigits) {
return true;
}
if (Character.isSpaceChar(c) && allowSpaceChar) {
return true;
}
return false;
}
};
}
Now you can use this filer like:
//Accept Characters Only
edit_text.setFilters(new InputFilter[]{getOnlyCharactersFilter()});
//Accept Digits and Characters
edit_text.setFilters(new InputFilter[]{getCharactersAndDigitsFilter()});
//Accept Digits and Characters and SpaceBar
edit_text.setFilters(new InputFilter[]{getCustomInputFilter(true,true,true)});
Bingo, I found the problem!
When I use android:cursorVisible="false" on the EditText the start and dstart parameters don't match up correctly.
The start parameter is still always 0 for me, but the dstart parameter is also always 0 so it works out as long as I use .replaceAll(). This is contrary to what this post says so I don't quite understand why but at least I can build something that works now!
We had a similar problem and I believe a solution[0] that would work for you as well. Our requirements were to implement an EditText that stripped rich text input. For example, if the user copied bold text to their clipboard and pasted it into the EditText, the EditText should remove the bold emphasis styling and preserve only the plain text.
The solution class looks something like this:
public class PlainEditText extends EditText {
public PlainEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
addFilter(this, new PlainTextInputFilter());
}
private void addFilter(TextView textView, InputFilter filter) {
InputFilter[] filters = textView.getFilters();
InputFilter[] newFilters = Arrays.copyOf(filters, filters.length + 1);
newFilters[filters.length] = filter;
textView.setFilters(newFilters);
}
private static class PlainTextInputFilter implements InputFilter {
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
int dstart, int dend) {
return stripRichText(source, start, end);
}
private CharSequence stripRichText(CharSequence str, int start, int end) {
// ...
}
}
}
Our original implementation for stripRichText() was simple:
// -- BROKEN. DO NOT USE --
String plainText = str.subSequence(start, end).toString();
return plainText;
The Java base String class doesn't retain any styling information so converting the CharSequence interface to a concrete String copies only plain text.
What we didn't realize was that some Android soft keyboards add and depend on temporary compositional hints for typos and other things. The problem manifests by removing the hints as well as repeating characters in an unexpected way (usually doubling the entire EditText field's input). The documentation[1] for InputFilter.filter() communicates the requirement this way:
* Note: If <var>source</var> is an instance of {#link Spanned} or
* {#link Spannable}, the span objects in the <var>source</var> should be
* copied into the filtered result (i.e. the non-null return value).
I believe the proper solution is to preserve temporary spans:
/** Strips all rich text except spans used to provide compositional hints. */
private CharSequence stripRichText(CharSequence str, int start, int end) {
String plainText = str.subSequence(start, end).toString();
SpannableString ret = new SpannableString(plainText);
if (str instanceof Spanned) {
List<Object> keyboardHintSpans = getComposingSpans((Spanned) str, start, end);
copySpans((Spanned) str, ret, keyboardHintSpans);
}
return ret;
}
/**
* #return Temporary spans, often applied by the keyboard to provide hints such as typos.
*
* #see {#link android.view.inputmethod.BaseInputConnection#removeComposingSpans}
* #see {#link android.inputmethod.latin.inputlogic.InputLogic#setComposingTextInternalWithBackgroundColor}
*/
#NonNull private List<Object> getComposingSpans(#NonNull Spanned spanned,
int start,
int end) {
// TODO: replace with Apache CollectionUtils.filter().
List<Object> ret = new ArrayList<>();
for (Object span : getSpans(spanned, start, end)) {
if (isComposingSpan(spanned, span)) {
ret.add(span);
}
}
return ret;
}
private Object[] getSpans(#NonNull Spanned spanned, int start, int end) {
Class<Object> anyType = Object.class;
return spanned.getSpans(start, end, anyType);
}
private boolean isComposingSpan(#NonNull Spanned spanned, Object span) {
return isFlaggedSpan(spanned, span, Spanned.SPAN_COMPOSING);
}
private boolean isFlaggedSpan(#NonNull Spanned spanned, Object span, int flags) {
return (spanned.getSpanFlags(span) & flags) == flags;
}
/**
* Apply only the spans from src to dst specific by spans.
*
* #see {#link android.text.TextUtils#copySpansFrom}
*/
public static void copySpans(#NonNull Spanned src,
#NonNull Spannable dst,
#NonNull Collection<Object> spans) {
for (Object span : spans) {
int start = src.getSpanStart(span);
int end = src.getSpanEnd(span);
int flags = src.getSpanFlags(span);
dst.setSpan(span, start, end, flags);
}
}
[0] Actual implementation available here: https://github.com/wikimedia/apps-android-wikipedia/blob/e9ddd8854ff15cde791a2e6fb7754a5450d6f7cf/app/src/main/java/org/wikipedia/richtext/RichTextUtil.java
[1] https://android.googlesource.com/platform/frameworks/base/+/029942f77d05ed3d20256403652b220c83dad6e1/core/java/android/text/InputFilter.java#37
I would just like to add my solution to the problem(as late as it is). I found that if you add
yourEditText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
Then the backspace problems stop

How to set inputfilter for edittext in android for decimal

I have edittext where I want to enter percentage value i.e decimals value, So I want to limit the user to enter only value less than 100 in the editText.
Example:1) if he want to enter value more than 100, it should not allow him to enter this.
2)If he wants t0 enter decimal value less than 100 i.e 50.5444 then it should allow him to enter this value
I have also found this link where I can set filter for integer value maximum to 100 but its not allowing me to enter decimal value
link: http://tech.chitgoks.com/2011/06/27/android-set-min-max-value-an-edittext-accepts/
Can anybody help me.
Hey you can use the TextWatcher, apply it to your EditText and implement the three methods it provides. Then, when you insert a new value, you can use a RegEx to check its validity.
Hope it helps!
Here is the full implementation code I used
editTextSpLinePercent.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
#Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
String enteredValue = s.toString();
if(checkNullValues(enteredValue)) {
if(Float.parseFloat(enteredValue.trim()) >100.0f){
AlertDialog.Builder builder = new AlertDialog.Builder(SpecialDiscountLineActivity.this);
// builder.setCancelable(true);
builder.setMessage("Percentage Value should be Less than 100");
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface arg0, int arg1) {
editTextSpLinePercent.setText("");
}
});
builder.show();
}
}
}
});
public static boolean checkNullValues(String valueToCheck) {
//Log.i("Log","CheckForNullValues : "+valueToCheck);
if(!(valueToCheck == null)) {
String valueCheck = valueToCheck.trim();
if(valueCheck.equals("") || valueCheck.equals("0") ) {
// Log.i("Log","Returning false 0 or Blank");
return false;
}
return true;
}
//Log.i("Log","Returning false null");
return false;
}
You can actually modify the helpful code sniplet provided in the link mentioned in the question as follows. Also optionally you can include the number of max decimal places you want the percentage to have and use the pattern matcher to return "" if the input doesn't match the intended pattern.
private class PercentageInputFilter implements InputFilter {
private float min;
private float max;
PercentageInputFilter(float min, float max) {
this.min = min;
this.max = max;
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
try {
// Get input
String stringToMatch = dest.toString() + source.toString();
float input = Float.parseFloat(stringToMatch);
// Check if the input is in range.
if (isInRange(min, max, input)) {
// return null to accept the original replacement in case the format matches and text is in range.
return null;
}
} catch (NumberFormatException nfe) {
}
return "";
}
private boolean isInRange(float min, float max, float input) {
return input >= min && input <= max;
}
}
You can use it like:
new PercentageInputFilter((float) 0.00, (float) 100.00);

How can I implement digit grouping input mask using InputFilter?

I am using InputFilter class to make a masked EditText supporting digit grouping. For example when the user inserts" 12345" I want to show "12,345" in EditText. How can I implement it?
This is my incomplete code:
InputFilter IF = new InputFilter() {
#Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
for (int i = start; i < end; i++) {
if (!Character.isLetterOrDigit(source.charAt(i))) {
return "";
}
}
if (dest.length() > 0 && dest.length() % 3 == 0)
{
return "," + source;
}
return null;
}
};
edtRadius.setFilters(new InputFilter[] { IF });
Is there any other way to implement this kind of input mask?
This an improvement on the response from #vincent. It adds checks on deleting spaces in a number in the format 1234 5678 9190 so when trying to delete a space it just moves the cursor backon character to the digit before the space. It also keeps the cursor in the same relative place even if spaces are inserted.
mTxtCardNumber.addTextChangedListener(new TextWatcher() {
private boolean spaceDeleted;
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// check if a space was deleted
CharSequence charDeleted = s.subSequence(start, start + count);
spaceDeleted = " ".equals(charDeleted.toString());
}
public void afterTextChanged(Editable editable) {
// disable text watcher
mTxtCardNumber.removeTextChangedListener(this);
// record cursor position as setting the text in the textview
// places the cursor at the end
int cursorPosition = mTxtCardNumber.getSelectionStart();
String withSpaces = formatText(editable);
mTxtCardNumber.setText(withSpaces);
// set the cursor at the last position + the spaces added since the
// space are always added before the cursor
mTxtCardNumber.setSelection(cursorPosition + (withSpaces.length() - editable.length()));
// if a space was deleted also deleted just move the cursor
// before the space
if (spaceDeleted) {
mTxtCardNumber.setSelection(mTxtCardNumber.getSelectionStart() - 1);
spaceDeleted = false;
}
// enable text watcher
mTxtCardNumber.addTextChangedListener(this);
}
private String formatText(CharSequence text)
{
StringBuilder formatted = new StringBuilder();
int count = 0;
for (int i = 0; i < text.length(); ++i)
{
if (Character.isDigit(text.charAt(i)))
{
if (count % 4 == 0 && count > 0)
formatted.append(" ");
formatted.append(text.charAt(i));
++count;
}
}
return formatted.toString();
}
});
In case you're still searching, I ran into this problem the last day, and found that using a TextWatcher is the best (still not really good) option. I had to group digits of credit card numbers.
someEditText.addTextChagedListener(new TextWatcher()
{
//According to the developer guide, one shall only edit the EditText's
//content in this function.
#Override
public void afterTextChanged(Editable text)
{
//You somehow need to access the EditText to remove this listener
//for the time of the changes made here. This is one way, but you
//can create a proper TextWatcher class and pass the EditText to
//its constructor, or have the EditText as a member of the class
//this code is running in (in the last case, you simply have to
//delete this line).
EditText someEditText = (EditText) findViewById(R.id.someEditText);
//Remove listener to prevent further call due to the changes we're
//about to make (TextWatcher is recursive, this function will be
//called again for every change you make, and in my experience,
//replace generates multiple ones, so a flag is not enough.
someEditText.removeTextChangedListener(this);
//Replace text with processed the processed string.
//FormatText is a function that takes a CharSequence (yes, you can
//pass the Editable directly), processes it the way you want, then
//returns the result as a String.
text.replace(0, text.length(), FormatText(text));
//Place the listener back
someEditText.addTextChangedListener(this);
}
#Override
public void beforeTextChaged(CharSequence s, int start, int count, int after)
{
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count)
{
}
});
My formatting function for the credit card numbers looked like this:
String FormatText(CharSequence text)
{
StringBuilder formatted = new StringBuilder();
int count = 0;
for (int i = 0; i < text.length(); ++i)
{
if (Character.isDigit(text.charAt(i)))
{
//You have to be careful here, only add extra characters before a
//user-typed character, otherwise the user won't be able to delete
//with backspace, since you put the extra character back immediately.
//However, this way, my solution would put a space at the start of
//the string that I don't want, hence the > check.
if (count % 4 == 0 && count > 0)
formatted.append(' ');
formatted.append(text.charAt(i));
++count;
}
}
return formatted.toString();
}
You might have to mind other issues as well, since this solution actually rewrites the EditText's content every time a change is made. For example, you should avoid processing characters you inserted yourself (that is an additional reason for the isDigit check).
use simple function:
public String digit_grouping(String in_digit){
String res = "";
final int input_len = in_digit.length();
for(int i=0 ; i< input_len ; i++)
{
if( (i % 3 == 0) && i > 0 )
res = "," + res;
res = in_digit.charAt(input_len - i - 1) + res;
}
return res;
}

Categories

Resources