Is there any simple way to set a 2 TextView dropdown to an AutoCompleteTextView.
There is android.R.layout.two_line_list_item
Which I couldn\'t find any exa
Here's an extension for AutoCompleteTextView, Kotlin
fun AutoCompleteTextView.showListDropDown(list: List<Any?>, action:(item: Any) -> Unit){
val adapter = ArrayAdapter<Any?>(
this.context,
R.layout.custom_dropdown_item,
ArrayList<Any?>(list)
)
this.setAdapter(adapter)
this.threshold = 1
this.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
val item = adapter.getItem(position)!!
action(item)
}
this.setOnTouchListener { _: View?, _: MotionEvent? ->
if (list.isNotEmpty()) {
if (this.text.toString() != "") adapter.filter
.filter(null)
this.showDropDown()
}
return@setOnTouchListener true
}
}
Always keep in mind that when you are customizing ArrayAdapter for your AutoCompleteTextView, you have to implement your own filtering method.
According to the documentation, the inferred type of setAdapter in AutoCompleteTextView is :
<T extends ListAdapter & Filterable> void setAdapter(T adapter)
Your adapter must be a ListAdapter (which BaseAdapter is, so far so good) and a Filterable, which BaseAdapter is not, nor is your Adapter implementation. I would extend an ArrayAdapter, which is Filterable, not to mention is would simplify your implementation (some of your methods duplicate methods of ArrayAdapter for the same result) :
public class TwoLineDropdownAdapter extends ArrayAdapter<TwoLineDropDown> {
private LayoutInflater mInflater = null;
private Activity activity;
public TwoLineDropdownAdapter(Activity a, ArrayList<TwoLineDropDown> items) {
super(a, 0, items);
activity = a;
mInflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public static class ViewHolder {
public TextView title;
public TextView description;
}
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.dropdown_text_twoline,
parent, false);
holder.title = (TextView) convertView
.findViewById(R.id.text1);
holder.description = (TextView) convertView
.findViewById(R.id.text2);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
return convertView;
}
}
Converted Dwivedi Ji's answer to Kotlin. I had some issue with Android Studio's auto convert. Thus, spent some time to make it work.
Now it is working. In case anyone needs it (in my case, I am filtering street names):
class StreetsAdapter( private val mContext: Context,
private val viewResourceId: Int,
private val items: ArrayList<Street>) : ArrayAdapter<Street?>(mContext, viewResourceId, items.toList()) {
private val itemsAll = items.clone() as ArrayList<Street>
private var suggestions = ArrayList<Street>()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var v: View? = convertView
if (v == null) {
val vi = mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
v = vi.inflate(viewResourceId, null)
}
val street: Street? = items[position]
if (street != null) {
val streetTitle = v?.findViewById(R.id.tvStreetTitle) as TextView?
streetTitle?.text = street.title
}
return v!!
}
override fun getFilter(): Filter {
return nameFilter
}
private var nameFilter: Filter = object : Filter() {
override fun convertResultToString(resultValue: Any): String {
return (resultValue as Street).title
}
override fun performFiltering(constraint: CharSequence?): FilterResults {
return if (constraint != null) {
suggestions.clear()
for (street in itemsAll) {
if (street.title.toLowerCase().startsWith(constraint.toString().toLowerCase())) {
suggestions.add(street)
}
}
val filterResults = FilterResults()
filterResults.values = suggestions
filterResults.count = suggestions.size
filterResults
} else {
FilterResults()
}
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
val filteredList = results?.values as ArrayList<Street>?
if (results != null && results.count > 0) {
clear()
for (c: Street in filteredList ?: listOf<Street>()) {
add(c)
}
notifyDataSetChanged()
}
}
}
}
And set your adapter:
val adapter = StreetsAdapter(this,
R.layout.item_street, //Your layout. Make sure it has [TextView] with id "tvStreetTitle"
arrayListOf() //Your list goes here
)
autoTextView.threshold = 1 //will start working from first character
autoTextView.setAdapter(adapter)
Here Code is working for me,
Set This adapter to autocompletetextview
AutoCompleteTextView etProductSearch = (AutoCompleteTextView)getView().findViewById(R.id.edtSearchBoxTakeOrder);
ProductSearchAdapter adapter = new ProductSearchAdapter(getActivity(), android.R.layout.simple_dropdown_item_1line, productList);
etProductSearch.setAdapter(adapter );
ProductSearchAdapter class
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.TextView;
public class ProductSearchAdapter extends ArrayAdapter<ProductDataModel> {
private ArrayList<ProductDataModel> items;
private ArrayList<ProductDataModel> itemsAll;
private ArrayList<ProductDataModel> suggestions;
private int viewResourceId;
@SuppressWarnings("unchecked")
public ProductSearchAdapter(Context context, int viewResourceId,
ArrayList<ProductDataModel> items) {
super(context, viewResourceId, items);
this.items = items;
this.itemsAll = (ArrayList<ProductDataModel>) items.clone();
this.suggestions = new ArrayList<ProductDataModel>();
this.viewResourceId = viewResourceId;
}
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(viewResourceId, null);
}
ProductDataModel product = items.get(position);
if (product != null) {
TextView productLabel = (TextView) v.findViewById(android.R.id.text1);
if (productLabel != null) {
productLabel.setText(product.getProductName());
}
}
return v;
}
@Override
public Filter getFilter() {
return nameFilter;
}
Filter nameFilter = new Filter() {
public String convertResultToString(Object resultValue) {
String str = ((ProductDataModel) (resultValue)).getProductName();
return str;
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
if (constraint != null) {
suggestions.clear();
for (ProductDataModel product : itemsAll) {
if (product.getProductName().toLowerCase()
.startsWith(constraint.toString().toLowerCase())) {
suggestions.add(product);
}
}
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) {
@SuppressWarnings("unchecked")
ArrayList<ProductDataModel> filteredList = (ArrayList<ProductDataModel>) results.values;
if (results != null && results.count > 0) {
clear();
for (ProductDataModel c : filteredList) {
add(c);
}
notifyDataSetChanged();
}
}
};
}
I believe that the easiest approach is to extend SimpleAdapter.
public class MyAdapter extends android.widget.SimpleAdapter {
static ArrayList<Map<String, String>> toMapList(Collection<MyObject> objectsCollection) {
ArrayList<Map<String, String>> objectsList = new ArrayList<Map<String, String>>(objectsCollection.size());
for (MyObject obj : objectsCollection) {
Map<String, String> map = new HashMap<String, String>();
map.put("name", obj.getName());
map.put("details", obj.getDetails());
objectsList.add(map);
};
return objectsList;
}
public MyAdapter(Context context, Collection<MyObject> objects) {
super(context, toMapList(objects),
R.layout.auto_complete_layout, new String[] {"name", "description"}, new int[] {R.id.name, R.id.description});
}
}
The major drawback is that this will bring candidates based on any space-delimited word in either name or description. If you add another field to your auto_complete_layout
, it will be involved in matching, too.
Therefore, I finished with rewriting the SimpleAdapter to better fit my needs, removing a significant amount of the base class overhead which was not relevant for my use case. But the few lines above give you a good start, and provide a solid reference to start customization from.