I have a MultiAutoCompleteTextView which presents a popup list when the user enters @ and a character.
When the user enters characters, the list will contain any of my test string that matches the characters entered. I can select an item from the list and Android puts that value in my text box.
If i move the cursor to the middle of the text and delete/change a character, Android popups the suggestion list again.
ISSUE
My issue is when i try and delete a character from the END of the text placed in the textview.
- If i type "@an" and select "animation" from the suggestion list, my text "@an" gets replaced with "@animation.
- The cursor is at the end of the inserted string
- If I then press the delete button the "@animation" text gets replaced with what i originally typed, i.e. "@an"
Why is it doing this?
I want a deletion off the end to behave the same as a deletion in the middle, i.e. delete that one character and if applicable popup the suggestion list again
CODE
MainActivity
package com.example.test;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.Spanned;
import android.view.View;
import android.widget.Button;
import android.widget.MultiAutoCompleteTextView;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
MultiAutoCompleteTextView multiAutoCompleteTextView;
TextView resultsTextView;
Button getResultsBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
multiAutoCompleteTextView = findViewById(R.id.test_text_view);
getResultsBtn = findViewById(R.id.get_results_btn);
resultsTextView = findViewById(R.id.results_text_view);
SuggestionsAdapter adapter = new SuggestionsAdapter(this, android.R.layout.simple_list_item_1);
multiAutoCompleteTextView.setAdapter(adapter);
multiAutoCompleteTextView.setThreshold(1);
// HAVE TO HAVE A TOKENIZER OTHERWISE IT DOES NOT WORK
multiAutoCompleteTextView.setTokenizer(new SuggestionsTokenizer());
getResultsBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Editable editable = multiAutoCompleteTextView.getText();
String stringValue = editable.toString();
Spanned[] spans = editable.getSpans(0, editable.length(), Spanned.class);
resultsTextView.setText("AutoCompleteTextView StringValue = " + stringValue + "\n\n" +
"More info");
}
});
}
}
Tokenizer
package com.example.test;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.widget.MultiAutoCompleteTextView;
public class SuggestionsTokenizer implements MultiAutoCompleteTextView.Tokenizer {
// start of token is the @ character
@Override
public int findTokenStart(CharSequence text, int cursor) {
int i = cursor;
while (i > 0 && text.charAt(i - 1) != '@') {
i--;
}
//Check if token really started with @, else we don't have a valid token
if (i < 1 || text.charAt(i - 1) != '@') {
return cursor;
}
return i;
}
@Override
public int findTokenEnd(CharSequence text, int cursor) {
int i = cursor;
int len = text.length();
while (i < len) {
if (text.charAt(i) == ' ') {
return i;
} else {
i++;
}
}
return len;
}
// Returns text, modified, if necessary, to ensure that it ends with a token terminator
// (for example a space or comma).
public CharSequence terminateToken(CharSequence inputText) {
int idx = inputText.length();
while (idx > 0 && inputText.charAt(idx - 1) == ' ') {
idx--;
}
if (idx > 0 && inputText.charAt(idx - 1) == ' ') {
return inputText;
} else {
if (inputText instanceof Spanned) {
SpannableString sp = new SpannableString(inputText + " ");
TextUtils.copySpansFrom((Spanned) inputText, 0, inputText.length(),
Object.class, sp, 0);
return sp;
} else {
return inputText + " ";
}
}
}
}
Adapter
package com.example.test;
import android.content.Context;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class SuggestionsAdapter extends ArrayAdapter<String> implements Filterable {
public static String[] suggestionValues = {"a", "ant", "apple", "asp", "android", "animation", "adobe",
"chrome", "chromium", "firefox", "freeware", "fedora"};
/**
* List of results.
*/
private List<String> m_resultList;
public SuggestionsAdapter(@NonNull Context context, int resource) {
super(context, resource);
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults filterResults = new FilterResults();
if (constraint != null) {
m_resultList = new ArrayList<>();
m_resultList.add("ALL"); // Special case@a
for (String s : suggestionValues) {
if (StringUtils.startsWithIgnoreCase(s, constraint.toString())) {
m_resultList.add(s);
}
}
// Assign the data to the FilterResults
filterResults.values = m_resultList;
filterResults.count = m_resultList.size();
}
return filterResults;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results != null && results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
@Override
public CharSequence convertResultToString(Object resultValue) {
return resultValue.toString();
}
};
}
@Override
public int getCount() {
return m_resultList.size();
}
@Override
public String getItem(int index) {
return (m_resultList != null) ? m_resultList.get(index) : null;
}
}
main layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<MultiAutoCompleteTextView
android:id="@+id/test_text_view"
android:layout_marginStart="8dp"
style="@style/my_suggestions_edittext"
android:layout_marginEnd="8dp"
android:padding="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:completionThreshold="1"
android:hint="Test the auto complete text view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<Button
android:id="@+id/get_results_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Get Results"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/test_text_view" />
<TextView
android:id="@+id/results_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="my_suggestions_results"
android:hint="This will contain info about the spans in the text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/get_results_btn" />
</androidx.constraintlayout.widget.ConstraintLayout>
