17

I would like to change the default filtering in AutoCompleteTextView. The default filtering finds all strings that startsWith the given token. My project requires that the filtering should find all strings that contains the given token.

Is it possible?

Nicolas Cortell
  • 659
  • 4
  • 16
Caffe Latte
  • 1,693
  • 1
  • 14
  • 32
  • Pls read my answer at http://stackoverflow.com/questions/32926034/autocompletetextview-not-completing-words-inside-parentheses/32928446#32928446 – BNK Jan 29 '16 at 08:42
  • Take a look at this solution also: http://stackoverflow.com/a/37298258/1808829 – Ayaz Alifov May 18 '16 at 11:31

6 Answers6

24

I found a solution for that, thanks to Google and searching for two days. As @torque203 suggested, I've implemented my own custom Adapter. First define a new XML file to custom Item in the adapter:

autocomplete_item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Medium Text"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        android:paddingTop="16dp"
        android:paddingBottom="16dp"
        android:id="@+id/lbl_name" />
</RelativeLayout>

Create new class for your Names:

Names

public class Names {
    public String name;
}

NamesAdapter

public class NamesAdapter extends ArrayAdapter<Names> {

    Context context;
    int resource, textViewResourceId;
    List<Names> items, tempItems, suggestions;

    public NamesAdapter(Context context, int resource, int textViewResourceId, List<Names> items) {
        super(context, resource, textViewResourceId, items);
        this.context = context;
        this.resource = resource;
        this.textViewResourceId = textViewResourceId;
        this.items = items;
        tempItems = new ArrayList<Names>(items); // this makes the difference.
        suggestions = new ArrayList<Names>();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = convertView;
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.autocomplete_item, parent, false);
        }
        Names names = items.get(position);
        if (names != null) {
            TextView lblName = (TextView) view.findViewById(R.id.lbl_name);
            if (lblName != null)
                lblName.setText(names.name);
        }
        return view;
    }

    @Override
    public Filter getFilter() {
        return nameFilter;
    }

    /**
     * Custom Filter implementation for custom suggestions we provide.
     */
    Filter nameFilter = new Filter() {
        @Override
        public CharSequence convertResultToString(Object resultValue) {
            String str = ((Names) resultValue).name;
            return str;
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            if (constraint != null) {
                suggestions.clear();
                for (Names names : tempItems) {
                    if (names.name.toLowerCase().contains(constraint.toString().toLowerCase())) {
                        suggestions.add(names);
                    }
                }
                FilterResults filterResults = new FilterResults();
                filterResults.values = suggestions;
                filterResults.count = suggestions.size();
                return filterResults;
            } else {
                return new FilterResults();
            }
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            List<Names> filterList = (ArrayList<Names>) results.values;
            if (results != null && results.count > 0) {
                clear();
                for (Names names : filterList) {
                    add(names);
                    notifyDataSetChanged();
                }
            }
        }
    };
}

SearchActivity (or your main activity)

....
   List<Names> namesList =  //your names list;
   NamesAdapter namesAdapter = new NamesAdapter(
                    SearchActivity.this,
                    R.layout.activity_search,
                    R.id.lbl_name,
                    namesList
            );
            //set adapter into listStudent
            autoCompleteTextView.setAdapter(namesAdapter);
            autoCompleteTextView.showDropDown();
...
Community
  • 1
  • 1
Caffe Latte
  • 1,693
  • 1
  • 14
  • 32
7

Here is a simpler, Kotlin version of what @Caffe Latte posted.

You don't need the custom layout file, just use the default android.R.layout.simple_list_item_1.

Provide any class to this adapter, including plain ol' Strings. It will simply use toString() to determine the display text.

import android.content.Context
import android.widget.ArrayAdapter
import android.widget.Filter
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import java.util.*

class AutoCompleteAdapter(
        context: Context,
        @LayoutRes resource: Int,
        @IdRes textViewResourceId: Int = 0,
        internal var items: List<Any> = listOf()
)
    : ArrayAdapter<Any>(context, resource, textViewResourceId, items) {


    internal var tempItems: MutableList<Any> = mutableListOf()
    internal var suggestions: MutableList<Any> = mutableListOf()

    /**
     * Custom Filter implementation for custom suggestions we provide.
     */
    private var filter: Filter = object : Filter() {

        override fun performFiltering(constraint: CharSequence?): FilterResults {
            return if (constraint != null) {
                suggestions.clear()
                tempItems.forEach {
                    if (it.toString().toLowerCase(Locale.getDefault()).contains(constraint.toString().toLowerCase(Locale.getDefault()))) {
                        suggestions.add(it)
                    }
                }

                val filterResults = FilterResults()
                filterResults.values = suggestions
                filterResults.count = suggestions.size
                filterResults
            } else {
                FilterResults()
            }
        }

        override fun publishResults(constraint: CharSequence?, results: FilterResults) {
            val filterList = results.values as? List<Any>
            if (results.count > 0) {
                clear()
                filterList?.forEach {
                    add(it)
                }.also {
                    notifyDataSetChanged()
                }
            }
        }
    }

    init {
        tempItems = items.toMutableList()
        suggestions = ArrayList()
    }

    override fun getFilter(): Filter {
        return filter
    }
}
Peter Keefe
  • 1,095
  • 14
  • 22
0

Yes, it's possible.

First way:

You should create a custom Adapter which implements ListAdapter and Filterable.

The Filter than can implement your "contains" filter logic.

And you set this adapter as the adapter to your AutoCompleteTextView.

Second way:

If you're already using ArrayAdapter. You could just override it's getFilter() method.

Gent Ahmeti
  • 1,559
  • 13
  • 17
0

Just to expand on the nice answer of Caffe Latte:

1)autoCompleteTextView.showDropDown(); is not needed.

2)To retrieve the input object one can use:

//retrieve the input in the autoCompleteTextView
        autoCompleteTextView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            //parent The AdapterView where the click happened.
            //view The view within the AdapterView that was clicked (this will be a view provided by the adapter)
            //position The position of the view in the adapter
            //id The row id of the item that was clicked.
            public void onItemClick(AdapterView<?> parent, View view, int position, long rowId) {
                String selection =parent.getItemAtPosition(position).toString();
                Toast.makeText(parent.getContext(),"" + selection,Toast.LENGTH_SHORT).show();
            }
        });

The object retrieved from parent must have a toString() method implemented.

Rexcirus
  • 2,459
  • 3
  • 22
  • 42
0

// try this

autoCompleteText.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View arg1, int position, long arg3) {
        YourCustomModel YourFilterdSelectedModel = (YourCustomModel) adapterView.getItemAtPosition(position);
        autoCompleteText.setText(str);
    }
});

£ This worked for me

Pankaj Talaviya
  • 3,328
  • 28
  • 31
0

This works just fine for me. I was using a standard ArrayAdapter for 4 AutoCompleteTextView's. This uses STARTS-WITH logic by default. I wanted to change just one of them to CONTAINS logic. Here is the code that works.

The old code was simply what is in the ELSE block; the new code is in the IF block for the one view I wanted to use Starts-With. The private class ContainsArrayAdapater... follows below.

        ArrayAdapter<String> dropdownAdapter;

        if (autoCompleteTextView.getId() == R.id.HotspotList) {
            // Create new adapter which uses CONTAINS searching instead of STARTS-WITH. 14-Aug-2023
            dropdownAdapter = new ContainsArrayAdapter(getApplicationContext(), R.layout.simple_dropdown_item, (ArrayList<String>) adapterList);
        } else {
            // Create new adapter for dropdown layout using default STARTS-WITH searching.
            dropdownAdapter = new ArrayAdapter<>(getApplicationContext(), R.layout.simple_dropdown_item, adapterList);
        }

        // Set Adapter on AutoCompleteTextView
        autoCompleteTextView.setAdapter(dropdownAdapter);

Here is the ArrayAdapter extension private class that is referenced above.

private class ContainsArrayAdapter extends ArrayAdapter<String> {

    private List<String> originalList;
    private List<String> filteredList;
    public ContainsArrayAdapter(@NonNull Context context, int resource, @NonNull ArrayList<String> objects) {
        super(context, resource, objects);
        this.originalList = new ArrayList<>(objects);
        this.filteredList = new ArrayList<>(objects);
    }

    @NonNull
    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence charSequence) {
                FilterResults filterResults = new FilterResults();
                if (charSequence == null || charSequence.length() == 0) {
                    filterResults.values = originalList;
                    filterResults.count = originalList.size();
                } else {
                    List<String> tempList = new ArrayList<>();
                    for (String item : originalList) {
                        if (item.toLowerCase().contains(charSequence.toString().toLowerCase())) {
                            tempList.add(item);
                        }
                    }
                    filterResults.values = tempList;
                    filterResults.count = tempList.size();
                }
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
                filteredList = (List<String>) filterResults.values;
                clear();
                addAll(filteredList);
                notifyDataSetChanged();
            }
        };
    }

Quite simple. I haven't found any problems in testing.

JJJones_3860
  • 1,382
  • 2
  • 15
  • 35
  • Update: Please note that this is Java and I did not wrap my TextView for the dropdown list in a Layout view. Not sure why others were doing this. – JJJones_3860 Aug 14 '23 at 20:16