I would need a way to detect if the EditText has been changed by the user typing something or by the app changing the text programmatically. Any standard way of doing this? I guess I could always do something hackish like unsetting the TextWatcher before setText() and setting it back again afterwards, but there's got to be a better way of doing this... right?
I tried checking if the EditText is focused in the TextWatcher, but that was of little help since the EditTexts gets focused "semi-randomly" anyway when scrolling...
Background
I have a ListView with EditTexts in every listitem. I've sorted out the basic problem of storing the values for the EditTexts for reuse when the user scrolls.
I also have a TextWatcher that sums up the values in all EditTexts and displays the sum when the user edits the content of any of the EditTexts.
The problem is that when I'm scrolling the list and my custom adapter is reentering the stored values in the EditTexts on bindView(), that also triggers the TextWatchers afterTextChanged() method, causing the scrolling to lag because the summing-up-function is triggered.
This sorted itself out a long time ago, but for anyone who finds their way here looking for an answer, here's what I did:
I ended up setting the Tag of the EditText to some arbitrary value right before I'm about to change it programmatically, and changing the value, and then resetting the Tag to null. Then in my TextWatcher.afterTextChanged() method I check if the Tag is null or not to determine if it was the user or the program that changed the value. Works like a charm!
Something like this:
edit.setTag( "arbitrary value" );
edit.setText( "My Text Value" );
edit.setTag(null);
and then
public void afterTextChanged(Editable s) {
if( view.getTag() == null )
// Value changed by user
else
// Value changed by program
}
The accepted answer is perfectly valid, but I have another approach;
#Override
public void onTextChanged(CharSequence charSequence,
int start, int before, int count) {
boolean userChange = Math.abs(count - before) == 1;
if (userChange) {
}
}
It works by checking if the change was a single character.
This is not a fool-proof solution as copy-paste operations might be missed, and non-user changes of a single character will also be missed.
Depending on your use case, this might be a viable solution.
One thing that helped to me is having boolean canListenInput field. Use it inside of watcher.
email.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable s) {
if (canListenInput) {
emailChanged = true;
}
}
});
Clear it before changing text programmatically. Set it inside of onAttachedToWindow, (after state) restoration:
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
canListenInput = true;
}
Depending on your use case (e.g. you are auto-populating this field when the user types into another field), you can also check if the view has focus, e.g.:
textView.doAfterTextChanged {
val isValueChangedByUser = textView.hasFocus()
// ...
}
I have created some extension methods to tackle this scenario
inline fun TextView.runTaggingCode(block: () -> Unit) {
this.setTag(R.string.tag_text_id, "set_from_code")
block()
this.setTag(R.string.tag_text_id, null)
}
fun TextView.isTaggedForCode() = this.getTag(R.string.tag_text_id) != null
where I have defined the R.string.tag_text_id as below
<string name="tag_text_id" translatable="false">dummy</string>
Now where I to use these methods, I will simply change my code as below,
override fun beforeTextChanged(
s: CharSequence, start: Int, count: Int,
after: Int,
) {
if (textView.isTaggedForCode()) {
return
}
textView.runTaggingCode {
// your logic here
}
}
But in case you don't want to change the same text view text, in it own TextWatcher you can also see the answer
You can do this by adding:
private String current = "";
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(!s.toString().equals(current)){
[your_edittext].removeTextChangedListener(this);
//Format your string here...
current = formatted;
[your_edittext].setText(formatted);
[your_edittext].setSelection(formatted.length());
[your_edittext].addTextChangedListener(this);
}
Related
I have been having issues with some odd behaviours from the EditText .setSelection that I am hoping you can all help with!
The app I am working on has a search field and there is the need to have it behave very similar to a browser search bar. For example, if the user types "fo", we would want the EditText to autocomplete to "foobar" with the autocompleted "obar" text highlighted so it can be easily replaced by the user incase the autocomplete does not match what the user was intending to type.
To accomplish this, I have an EditText field with a TextWatcher setup to try to autocomplete the text afterTextChanged. The following is my code:
editText.addTextChangedListener(new TextWatcher() {
int lastCount = 0;
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable editable) {
editText.removeTextChangedListener(this);
String searchString = editable.toString();
if (editable.length() > lastCount) {
lastCount = editable.length();
int oldLength = searchString.length();
String autoFillResult = completeAutofill(searchString);
if (!autoFillResult.equals("")) {
searchString = autoFillResult;
editable.clear();
editable.append(autoFillResult);
editText.setSelection(oldLength, autoFillResult.length());
}
} else lastCount = editable.length();
editText.addTextChangedListener(this);
}
});
My issue is as follows. Using the previous "Foobar" case:
The user types "F", the EditText autofills "oobar" and highlights it.
Then the user types the first "o"
The EditText field is momentarily cleared (i.e. afterTextChanged receives an Editable with the empty string)
The EditText correctly autofills to user supplied "Fo" followed by autocompleted "obar" which is highlighted.
The issue is the EditText is being cleared then repopulated when the user types the next character, which creates a noticeable disturbance in the EditText field. Interestingly, I have singled out the editText.setSelection(oldLength, autoFillResult.length()); as the culprit (i.e. commenting out the line gets rid of the issue, but obviously its the wrong functionality).
After completing some Google research and my own debugging I am still unsure why this is happening. The issue does not appear excessively common as I could not find it on Google and I could not figure out the reason for this issue in my own experimentation.
Thank you in advance!
the if condition should possibly be:
autoFillResult != null && !autoFillResult.equals("")
while you might be looking for (or attempting to recreate) an AutoCompleteTextView.
Try removing editable.clear();
I have a list of items. In each item's row I have 2 EditTexts side-by-side. EditText-2 depends on EditText-1's value. This list is bound with data-binding values in HashMap<String, ItemValues>
For Example:
Total _____1000____
Item A __1__ __200__
Item B __1__ __200__
Item C __1__ __200__
Item D __2__ __400__
First EditText is the share and the second value is its value calculated based on total and share. So, in example if I change any 1 share, all the values will be changed. So, shown in example total no of shares are = 1+1+1+2 = 5. So amount per share = 1000/5 = 200 and is calculated and shown in next EditText.
I have bound this values with two-way data binding like this:
As, this is a double value, I have added 2 binding adapters for this like this:
#BindingAdapter("android:text")
public static void setShareValue(EditText editText, double share) {
if (share != 0) {
editText.setText(String.valueOf(share));
} else {
editText.setText("");
}
}
#InverseBindingAdapter(attribute = "android:text")
public static double getShareValue(EditText editText) {
String value = editText.getText().toString();
if (!value.isEmpty()) {
return Double.valueOf(value);
} else
return 0;
}
Now, to calculate new values, I need to re-calculate whole thing after any share value is changed. So, I added android:onTextChagned method to update Calculations. But it gets me an infinite loop.
<EditText
android:text="#={items[id].share}"
android:onTextChanged="handler.needToUpdateCalculations"
.... />
public void needToUpdateCalculations(CharSequence charSequence, int i, int i1, int i2) {
updateCalculations();
}
This gets an infinete loop because when data changes, it is rebound to the EditText, and each EditText has an onTextChanged attached it will fire again and it will get really large - infinite loop.
It also updates the value of itself, ended up loosing the cursor as well.
I have also tried several other methods like adding TextWatcher when on focus and removing when losses focus. But at least it will update it self and will loose the cursor or infinite loop.
Unable to figure this problem out. Thank you for looking into this problem.
EDIT:
I have tried with the below method. But, it doesn't allow me to enter . (period).
#BindingAdapter("android:text")
public static void setDoubleValue(EditText editText, double value) {
DecimalFormat decimalFormat = new DecimalFormat("0.##");
String newValue = decimalFormat.format(value);
String currentText = editText.getText().toString();
if (!currentText.equals(newValue)) {
editText.setText("");
editText.append(newValue);
}
}
The reason you stated is correct and it will make a infinite loop definitely. And there is a way to get out from the infinite loop of this problem, android official provided a way to do so (But it is not quite obvious.)(https://developer.android.com/topic/libraries/data-binding/index.html#custom_setters)
Binding adapter methods may optionally take the old values in their
handlers. A method taking old and new values should have all old
values for the attributes come first, followed by the new values:
#BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
You can use the old value and new value comparison to make the setText function called conditionally.
#BindingAdapter("android:text")
public static void setShareValue(EditText editText, double oldShare,double newShare) {
if(oldShare != newShare)
{
if (newShare!= 0) {
editText.setText(String.valueOf(newShare));
} else {
editText.setText("");
}
}
}
This question already has an answer here:
automatically update my activity to show which was written
(1 answer)
Closed 8 years ago.
Using a text field, how would I set the text of a TextView while constantly updating the text? For example: The user begins to type information into the text field, this changes some text in in the activity they're in, however, the user does not need to manually update the text, instead the text automatically refreshes.
I have tried doing this myself and searched for other dilemmas, yet nothing appears to work. Additionally, I'm working with fragments that could possibly cause the problem. The code is below, partitioned into areas, before onCreateView(), in onCreateView() and after onCreateView().
Before onCreateView():
// Edit Text
EditText exerciseOneTitle;
// String value, contains value of exerciseOneTitle
String value;
// titleOne, what I want to the user to be able to change
TextView titleOne;
During onCreateView():
// Edit text
exerciseOneTitle = (EditText) rootView.findViewById(R.id.exercise_text);
// Get Edit Text value
value = exerciseOneTitle.getText().toString();
// Set title so there is always an initial value
titleOne.setText(R.string.exercise_one);
// Checks if edit text is focused
exerciseOneTitle.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
// While edit text is focused, update titleOne via refresh()
while (hasFocus == true) {
refresh();
}
}
});
After onCreateView():
// Get the updated "value" and set it as titleOne text
// Additionaly, will be using it in other situations
public void refresh() {
value = exerciseOneTitle.getText().toString();
titleOne.setText(value);
}
Any thoughts or ideas? Thanks!
You can use TextWatcher.. See the link
According to the docs
onTextChanged() is called to notify you that, within s, the count
characters beginning at start have just replaced old text that had
length before. It is an error to attempt to make changes to s from
this callback.
Try this..
tv = (TextView)findViewById(R.id.charCounts);
textMessage = (EditText)findViewById(R.id.textMessage);
textMessage.addTextChangedListener(new TextWatcher(){
public void afterTextChanged(Editable s) {
tv.setText(textMessage.getText().toString());
}
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
public void onTextChanged(CharSequence s, int start, int before, int count){}
});
I'm using Android's AutoCompleteTextView with a CursorAdapter to add autocomplete to an app. In the view's onItemClickListener() (i.e. when the user touches one of the autocompleted drop down items) I retrieve the text and place it in the EditText so that the user can modify it if they need to.
However, when I call setText() on the TextView the autocomplete behavior is triggered and the dropdown shows again. I'd like to only show the dropdown if the user types new text with the keyboard. Is there a way to do this?
You can use the dismissDropDown() method of the AutoCompleteTextView object. Take a look at the documentation.
When we click on item suggested in AutoCompleteTextView.onTextChanged() is performed before onItemClick
So, to avoid this try below code..
autocompletetextview.addTextChangedListener(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) {
if (autocompletetextview.isPerformingCompletion()) {
// An item has been selected from the list. Ignore.
} else {
// Perform your task here... Like calling web service, Reading data from SQLite database, etc...
}
}
#Override
public void afterTextChanged(final Editable editable) {
}
});
If you wish to dissmis AutoCompleteTextView's dropdown you should use its post(Runnable r) method. It works for me :)
Here is an example:
mAutoCompleteTextView.post(new Runnable() {
public void run() {
mAutoCompleteTextView.dismissDropDown();
}
}
Answering my own question after a couple hours of hacking at this: It turns out you should implement your own OnItemClickListener and instead rely on the existing click listener to populate the TextView. I had originally implemented the onItemClickListener because it was using the results of Cursor.toString() to populate the text view. To change the output String, you should implement convertToString(Cursor) in your CursorAdapter. The CharSequence that gets returned will be populated in the text view.
Doing this will also prevent the dropdown from showing up again (since setText() triggers the completion behavior but the default onItemClickListener does not).
Different approach.
I agreed dismissDropDown() works but in my case, it wasn't working as expected. So, I used:
autoCompleteTextView.setDropDownHeight(0);
And if you want to show the dropdown list again, you an use
autoCompleteTextView.setDropDownHeight(intValue);
I have a situation where I would like the user to complete a sentence for me. For example, consider a EditText with a hint of "The last time I ". Normally, when a user clicks an EditText, the hint disappears, but I would like it to stay. Additionally, I would like the text to be permanent, so that it cannot be erased... leaving the user with only one option... complete the sentence.
The first part is fairly simple, just use the setText() method of EditText to place the hint. The difficult part is the latter. How can I have text in an EditText that the user cannot erase?
Well couldn't you do it in code? Some algorithim like, if the text is less than 16 characters (length of "The last time I ") then set the text to that. Therefore whenever they clicked it, if they tried to erase it, it would just go back to the default text.
Also, another idea..why don't you just make a TextView thats right edge aligns with the left edge of the EditText box, the user would never know that it was another box. This is acutally the best solution, if you don't want the text ever to be edited, just make it a TextView
Described problem can be solved using android.text.TextWatcher.
public class CompleteSentenceWathcher implements TextWatcher {
private final String initialText;
private int start;
private int after;
private int count;
public CompleteSentenceWathcher(String initialText) {
this.initialText = initialText;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
this.start = start;
this.count = count;
this.after = after;
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
if(start < initialText.length()) {
if(s.toString().startsWith(initialText)) {
return;
}
if(count >= 1 && after == 0) {
if(start+count+1 <= initialText.length()) {
s.replace(start, start+count, initialText.substring(start, start+count+1));
} else {
s.replace(start, start, initialText.substring(start, start+1));
}
} else if(count == 0 && after >= 1) {
s.delete(start, start+after);
}
}
}
}
Create an instance of EditText and add the TextWatcher.
EditText editText = new EditText(this);
editText.setText("I love");
editText.addTextChangedListener(new CompleteSentenceWathcher(editText.getText().toString()));
I've implemented this with an InputFilter, where _PERMANENT_HINT_TEXT is the text at the end of the EditText that I don't want the user to be able to modify. I recommend adding a color span to it, so that it is grayed out to hopefully look like a hint/disabled section of text. This should hopefully improve the UX as they should automatically assume it is unmodifiable, and not just wonder why some part of the EditText (that they usually can completely change) isn't "working". This approach allowed the text to be set after
the InputFilter was set on the EditText, which was a requirement for me since I used this on an EditTextPreference.
To be clear, I needed the permanent text to exist at the end of the EditText, instead of the beginning, but that should be symmetrical to my implementation.
new InputFilter() {
#Override
public CharSequence filter(CharSequence source, int source_start, int source_end,
Spanned destination, int destination_start, int destination_end) {
final int protected_text_start = (TextUtils.isEmpty(destination)? source.length() : destination.length()) - _PERMANENT_HINT_TEXT.length();
// Allows input into unprotected region
if (source_start + destination_start - source_end < protected_text_start)
return null;
// Prevents deletion of protected region
else if (TextUtils.isEmpty(source))
return destination.subSequence(destination_start, destination_end);
// Ignores insertion into protected region
else
return "";
}
}
use EditText.setFilters(new InputFilters[] { /* InputFilter goes here */ }; to add it to the desired EditText.
Just checking for the length wouldn't be adequate... I could type "This is a really long text I put into the box" and it would accept it even though it doesn't begin with "The last time I" string.
Personally, I would probably go for the prevention method suggested of using a TextView over that of a check on the way out. But if you're going to validate it afterwards, you'd actually need to check the beginning of the returned string.