I have a TextWatcher set on an EditText that changes the input type after a user types a number followed by a space.
If the user types two numbers the input type switches and accepts the next character, but if the user types only one number and presses space the input type still changes but it won't accept the first character the user tries to input.
I've tested this on Froyo and 1.6, it only happens on Froyo, 1.6 works like it should.
Here's the code:
TextWatcher watcher = new TextWatcher() {
#Override
public void afterTextChanged (Editable s) {
}
#Override
public void beforeTextChanged (CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged (CharSequence s, int start, int before, int count) {
// Parsed text holder is a class that just parses the EditText and pulls out various parts.
ParsedTextHolder th = parseTextHolder(s);
String newText = "";
Boolean setTextKeyListener = false;
String tGetTextString = mQuery.getText().toString();
if (!th.pFullMatch.equals("")) {
if (th.pFullMatch.length() == 2) {
mQuery.setKeyListener(new
TextKeyListener(TextKeyListener.Capitalize.SENTENCES, true));
newText = tGetTextString + " for ";
setTextKeyListener = true;
}
}
if (setTextKeyListener) {
Log.i("setTextKeyListener", "true");
if (mQuery.getKeyListener().getClass() != TextKeyListener.class) {
mQuery.setKeyListener(new TextKeyListener(TextKeyListener.Capitalize.SENTENCES, true));
} else {
Log.d("setTextKeyListener", "skipped. already was text.");
}
if (!newText.equals("")) {
int position = newText.length();
String ttext = newText;
newText = "";
mQuery.setText(ttext, TextView.BufferType.EDITABLE);
mQuery.setText(ttext);
Editable text = mQuery.getEditableText();
Log.w("setting selectiont to text: ", text.toString());
Log.w("setting selectiont to position: ", Integer.toString(position));
Selection.setSelection(text, position);
mQuery.setKeyListener(new TextKeyListener(TextKeyListener.Capitalize.SENTENCES, true));
}
}
}
};
Also, here's an APK if you want to see what the bug is like: http://endlesswhileloop.com/files/KeyboardBug.apk
Is mQuery the editText that is being watched? According to the javadocs, you shouldn't be making any changes to the text in your EditText in onTextChanged. All such changes should be made in afterTextChanged.
Generally, I've ended up examining the change in onTextChanged and then doing the work that results form the change in afterTextChanged. You might try that.
Related
I'm developing a simple app that flips the word in a sentence. I need it to have three ways of doing that depending on mode the user chooses by enabling RadioButtons. So I use RadioGroup as parent layout to be able to enable one RadioButton at a time.
I can achieve this simply by switching their IDs.
modeGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId){
case R.id.rb_mode_1:
String[] nowTyping = input.getText().toString().split(" ");
ArrayList<String> wordArray = new ArrayList<>();
for (String word : nowTyping){
wordArray.add(0, word);
}
String invertedSentence = TextUtils.join(" ", wordArray);
output.setText(invertedSentence);
break;
//And so...
}
}
});
Now as the output text is printed as the user is typing, I use TextWatcher to get what user is typing directly be shown into a textView. Now I'm unable to change mode because the code that flips the words is actually called from onTextChanged() method that the class implements.
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String[] nowTyping = input.getText().toString().split(" ");
ArrayList<String> wordArray = new ArrayList<>();
for (String word : nowTyping){
wordArray.add(0, word);
}
String invertedSentence = TextUtils.join(" ", wordArray);
output.setText(invertedSentence);
}
My question is, how can I achieve my need when also using TextWatcher? Actually I can either change mode without being able to get texts by output in real time or have texts be output without being able to change mode.
I solved this using getCheckedRadioButtonId() method of RadioGroup directly inside onTextChanged() method of TextWatcher like below:
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (modeGroup.getCheckedRadioButtonId() == R.id.rb_mode_1) {
String[] nowTyping = input.getText().toString().split(" ");
ArrayList<String> wordArray = new ArrayList<>();
for (String word : nowTyping) {
wordArray.add(0, word);
}
String invertedSentence = TextUtils.join(" ", wordArray);
output.setText(invertedSentence);
}
else if (modeGroup.getCheckedRadioButtonId() == R.id.rb_mode_2){
//Do mode 2 stuffs...
}
....
}
Sometimes we just don't dig enough before asking other to do for us.
I have an EditText which is supposed to have multi-line while typing and should have an action done when enter is pressed, meaning the soft keyboard should disappear and the cursor made invisible... basically the EditText should lose "focus".
Now this is done and working, but the problem is that the "enter" key leaves a new line in the EditText.
I have tried to remove it by setting the entire text to "", but the EditText is blank with a new line.
I have tried to remove it by replacing '\n' with a 's' and setting the text back, but the text starts in a new line.
String cap = caption.getText().toString().replace('\n', 's').trim();
caption.setText(cap);
Thanks in advance.
I know this is a late answer but maybe it can help someone.
I recently had this problem and I solved it by implementing an addTextChangedListener to my EditText.
Code:
EditText editText = new EditText(this);
layout.addView(editText);
editText.addTextChangedListener(new TextWatcher() {
boolean ignore = false; // This is used to prevent infinite recursion in the afterTextChanged method
#Override
public void afterTextChanged(Editable arg0) {
if (ignore) return;
ignore = true;
String s = arg0.toString();
if (s.length() > 0) {
// The condition checks if the last typed char's ASCII value is equal to 10, which is the new line decimal value
if (((int)(s.charAt(s.length()-1)) == 10)) {
String newStr = s.substring(0, s.length()-1); // Removes the new line character from the string
editText.setText(newStr);
editText.setSelection(editText.length()); // Sets the text cursor to the end of the text
}
}
ignore = false;
}
#Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
There might be better ways to get rid of the new line character, but this worked for me and seems simple enough.
What I'm trying to achieve is to have an EditText with the following behaviour:
At first, the text show:
0.00 (en_GB local) or 0,00 (fr_FR local)
When user type the number 4, the Edittext show:
0.04 (en_GB) or 0,04 (fr_FR)
When user type another 6:
0.46 (en_GB) or 0,46 (fr_FR)
Etc.
Currently my code works very fine when the local is set to en_GB, however when the local change to fr_FR, I get an infinite loop.
Here is the code:
EditText layout:
<EditText
android:id="#+id/amount_ET"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:maxLength="8" />
And the java code:
amountEditText = (EditText) findViewById(R.id.amount_ET);
amountEditText.setText(mValueToString());
// ensure the cursor is always in the beginning of the text.
amountEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (v.hasFocus()) {
int len = amountEditText.getEditableText().toString().trim().length();
if (len > 1) {
amountEditText.setSelection(0);
amountEditText.setCursorVisible(false);
}
}
}
});
// add listener in order to intercept input on the EditText
amountEditText.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable s) {
// we only update if the value is not what we expect to avoid infinite loop
if (!s.toString().isEmpty() && !s.toString().equals(mValueToString())) {
// the cursor is always at the first position, so new char are in the beginning of the editable
updateAmountTextView(s, s.charAt(0));
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
}
/**
* Update the new value with the given numeral and update the given Editable with the new value.
*
* #param s
* if not null, will be updated with the formatted value.
* #param c
* the char representing the number to insert.
*/
protected void updateAmountTextView(Editable s, char c) {
Integer i = Integer.valueOf(c - 48);
mValue = mValue * 10 + i;
if (s != null) {
s.clear();
s.append(mValueToString()); // when using fr_FR local, the coma is not appended leading to a false value in the editable.
}
amountEditText.setSelection(0);
}
/**
* return the String formatted value.
*/
protected String mValueToString() {
Double d = Double.valueOf(mValue) / 100;
NumberFormat formatter = new DecimalFormat("#0.00");
return formatter.format(d);
}
Has explained in the comments of the java code, the bug is in the method updateAmountTextView() where the Editable refuse to take the coma.
When changing the inputType of the EditText to the value text this bug doesn't occur, except I want to have a softkeyboard accepting numbers only.
I guess I should do something on the Editable object before appending value to it but I can't find anything about this in the documentation.
As pointed in the comments by https://stackoverflow.com/users/427291/devisnik adding the line android:digits="0123456789.," in the Edittext layout solved the issue.
I have provided an AutoCompleteTextView to show suggestions to the user. Based on the item selected by the user, I am getting the ID of the item and using it in data base side. Now my problem is to force the user to make selection only from AutoCompleteTextView (i.e. user should not enter his own text). It is a client requirement. How to do this?
Here's a pretty straightforward solution:
You can create a variable to store the selected value by setting setOnItemClickListener in your AutoCompleteTextView. Then you can null that value whenever a user types in the field by adding a TextWatcher to it. Finally, you can validate your variable is not null before continuing.
String my_var; //keep track!
AutoCompleteTextView tv = (AutoCompleteTextView) layout.findViewById(R.id.tv);
tv.setAdapter(my_adapter);
tv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
my_var = my_adapter.getItem(position).toString();
}
});
/**
* Unset the var whenever the user types. Validation will
* then fail. This is how we enforce selecting from the list.
*/
tv.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
my_var = null;
}
#Override
public void afterTextChanged(Editable s) {}
});
I happened to need such a requirement for a project I am working on, and I though I'll share you guys the way I implemented the required.
I added a on focus change listener for the auto-complete text view and checked when the user has focus changed focus from the auto-complete, and handled the situation straight forward.
autoTextViewCountry.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, boolean b) {
if(!b) {
// on focus off
String str = autoTextViewCountry.getText().toString();
ListAdapter listAdapter = autoTextViewCountry.getAdapter();
for(int i = 0; i < listAdapter.getCount(); i++) {
String temp = listAdapter.getItem(i).toString();
if(str.compareTo(temp) == 0) {
return;
}
}
autoTextViewCountry.setText("");
}
}
});
So my implementation is: if the typed text doesn't exist in the array adapter then on focus changed empty the text view, and later on when continuing to next stage of say registration, check if this text view is empty or not.
Hope this approach helps somebody.
Happy coding.
NiceAutoCompleteTextView will give you the ability to check whether a selection was made from the drop-down popup, by invoking isSelectionFromPopup()
Just add this property to your AutoCompleteTextView.
android:focusable="false"
My Code looks like:
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/menu"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Mode">
<AutoCompleteTextView
android:id="#+id/mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
/>
</com.google.android.material.textfield.TextInputLayout>
On Java Side:
AutoCompleteTextView mode = findViewById(R.id.mode);
final List<String> modeList = new ArrayList();
modeList.add("YEARLY");
modeList.add("HALF-YEARLY");
modeList.add("QUARTER-YEARLY");
modeList.add("MONTHLY");
mode.setAdapter(new ArrayAdapter(getApplicationContext(),R.layout.list_item,modeList));
To get the Text of AutoCompleteTextView:
mode.getText().toString()
Ok I assume you would like to limit the input of the user to the texts contained in the list of items listed in the suggest box.
For instance if you have:
One
Two
Three
then the user could only type for the first character "O" and "T".
And so on according to the text entered before.
To achieve this you can utilize the setFilters method of TextView:
editBox = (TextView) findViewById(R.id.editBox);
editBox.setFilters(getFilters());
editBox.addTextChangedListener(this);
editBox.setOnFocusChangeListener(this);
Additionally you would probably need the text change listener and focus listener to react and update the filtered list when a new character is entered ... plus to update the filter.
Here is an example of decimal number filter I have used on a project:
protected InputFilter[] getFilters()
{
InputFilter[] filters = new InputFilter[1];
filters[0] = new InputFilter()
{
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)
{
// limit input to digits and decimal / thousand separator only
// in case thousand separator is pressed, change it to decimal
// separator instead
String output = "";
if (number.isFocused())
{
for (int i = start; i < end; i++)
{
char c = source.charAt(i);
if (isDecimalOrThousandSeparator(c))
{
output = output + Character.toString(decimalSeparator);
}
else if (Character.isDigit(c))
{
output = output + Character.toString(c);
}
}
return output == "" ? null : output;
}
return null;
}
};
return filters;
}
A simple solution would be to just check if the current input is one of the items in the adapter. You can do it like this:
val AutoCompleteTextView.isValid: Boolean
get() {
for (i in 0 until adapter.count) {
if (adapter.getItem(i) == text.toString()) {
return true
}
}
return false
}
here is the another solution for this proAutoCompleteTextView.Validator to ensure the valid values. Validator is invoked when edit text looses focus.
autoCompleteTextView.validator = object : AutoCompleteTextView.Validator {
override fun isValid(text: CharSequence?): Boolean {
return optionsList.contains(text.toString())
}
override fun fixText(invalidText: CharSequence?): CharSequence {
return ""
}
}
Where optionsList is list of valid values.
I was having the same requirement, so here is my implementation:
autoCompleteTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
autoCompleteTextView.showDropDown();
}
});
In the xml set focusable=false and focusableInTouchMode=false.
Happy coding
In order to have a non editable variation of the AutoCompleteTextView, you should disable user input in the AutoCompleteTextView. That can be achieved by setting android:inputType="none" on the AutoCompleteTextView.
I'm using a AutoCompleteTextView, the default behavior of the backspace button goes something like this.
Say i type "Ar", this gives me a suggestion "Argentina", i select "Argentina" from the drop down...The Text now becomes "Argentina ". But say i need to remove the last character, so I hit backspace on the keyboard, the AutcompleteTextView removes all the text till the point i typed (ie. the text now becomes "Ar" again).
How do i eliminate this behavior and let the text in the AutoComplete to behave normally?
At first I thought it was some kind of SpannableString so i called "clearSpans()" but it doesn't seem to work. Any pointers?
Thanks in advance. :)
I think you use the MultiAutoCompleteTextView which add the setTokenizer(new SpaceTokenizer()).
If you use
AutoCompleteTextView instead of MultiAutoCompleteTextView and remove the setTokenizer(...)
the problem will be gone.
I did not find any solution, but finally I figured out, this code worked for me:
editText.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
try {
// removing QwertyKeyListener.Replaced span
Editable text = editText.getText();
Object[] spans = text.getSpans(0, text.length(), Object.class);
if (spans != null) {
for (int i = spans.length - 1; i >= 0; i--) {
Object o = spans[i];
String desc = "" + o; // This is a hack, not a perfect solution, but works. "QwertyKeyListener.Replaced" is a private type
if (desc.indexOf("QwertyKeyListener$Replaced") != -1) {
text.removeSpan(o);
}
}
}
} catch (Throwable e) {
MyUtil.msgError(e);
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});