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>

enter image description here

0

There are 0 best solutions below