Custom Getfilter in Custom Arrayadapter in Android

Custom getFilter in custom ArrayAdapter in android

You are having problem, mainly because you are using custom object. If you pass a String or int value to array adapter its know how to filter it. But if you pass custom object default filter implementation have to no idea how to deal with that.

Although it is not clear what you are trying to do in your filter i recommend you following steps.

  1. Proper implementation of ListTO, although it has nothing to do with your goal right now
  2. Implement custom filter
  3. return your filter

Implement custom filter

First thing you have to do is, implements Filterable from your array adapter.

Second, provide implementation of your Filter

Filter myFilter = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults filterResults = new FilterResults();
ArrayList<ListTO> tempList=new ArrayList<ListTO>();
//constraint is the result from text you want to filter against.
//objects is your data set you will filter from
if(constraint != null && objects!=null) {
int length=objects.size();
int i=0;
while(i<length){
ListTO item=objects.get(i);
//do whatever you wanna do here
//adding result set output array

tempList.add(item);

i++;
}
//following two lines is very important
//as publish result can only take FilterResults objects
filterResults.values = tempList;
filterResults.count = tempList.size();
}
return filterResults;
}

@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence contraint, FilterResults results) {
objects = (ArrayList<ListTO>) results.values;
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
};

Last step,

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

getFilter() on a custom ArrayAdapter not working

ArrayAdapter's built-in Filter uses the toString() return from the model class (i.e., its type parameter) to perform its filtering comparisons. You don't necessarily need a custom Filter implementation if you're able to override User's toString() method to return what you want to compare (provided its filtering algorithm is suitable to your situation). In this case:

@Override
public String toString() {
return name;
}

To be clear on exactly what that algorithm is, ArrayAdapter's default filtering goes as follows:

The filter String is first converted to lowercase. Then, looping over the dataset, each value's toString() return is converted to lowercase, and checked to see if it startsWith() the filter String. If so, it's added to the result set. If not, a second check is performed, whereby the value's lowercase String is split on a space (" "), and each value from that is compared to the filter, again using startsWith(). Basically, it first checks if the whole thing starts with the filter text, and then checks each word, if necessary.

If that's a suitable filter, then this solution is by far the simplest.


If that does not meet your needs, and you do actually need a custom Filter implementation, then you should just not use ArrayAdapter to begin with. ArrayAdapter maintains internal, private Lists for the original and filtered collections – initially populated from the collection passed in the constructor call – and you do not have access to those. This is why the custom Filter attempt shown does not work, as the displayed item count and the item returned from getItem(position) are coming from that internal filter List, not the one built in the custom Filter.

In that case, you should directly subclass BaseAdapter instead, maintaining your own Lists for the original and filtered collections. You can use ArrayAdapter's source as a guide.

Indeed, ArrayAdapter is often the wrong choice when choosing an Adapter to extend. ArrayAdapter is designed for a singular, somewhat simplistic goal: setting a flat String on a single TextView in each list item. There are several cases in which subclassing ArrayAdapter instead of BaseAdapter is rather pointless and/or redundant. For example:

  • Overriding getView() and not using the View returned from a call to super.getView().
  • Manually setting the text on the TextView yourself, for whatever reason.
  • Maintaining and using your own collections; i.e., the arrays, or Lists, or what have you.

In these and certain other cases, it's arguably better to use BaseAdapter from the start. Using ArrayAdapter for anything much more complex than single text items with basic functionality can quickly become cumbersome and error-prone, and is often more trouble than it's worth.


Lastly, I would mention that ListView is basically deprecated, at this point, though not yet officially, at the time of this writing. Current recommendations are to use RecyclerView instead. However, for those brand new to Android programming, ListView can still be useful as a beginning step in understanding the overall design of this type of recycling adapter View. RecyclerView can be a little overwhelming to start with.

Custom ArrayAdapter getFilter() not being called

The solution ended up being a custom ArrayAdapter subclass which prepends a filter before the ArrayFilter even sees it.

The change to the main application is minimal, it just needs to instantiate the newly-christened WtfArrayAdapter and pass its constructor an extra argument: an object reference to the object containing the normalisation method (in the current äpp’s design, that’s the MainActivity class, a future refactor will do better):

-        ArrayAdapter acronymKeys = new ArrayAdapter(this,
- android.R.layout.simple_list_item_1, sorted);
+ ArrayAdapter acronymKeys = new WtfArrayAdapter(this,
+ android.R.layout.simple_list_item_1, sorted, this);

The custom WtfArrayAdapter must be able to call methods from ArrayFilter (which is ArrayAdapter’s internal private class), so it needs to belong to package android.widget;. I’ll spare you the full imports, comments, etc. and will reproduce the important parts below:

public class WtfArrayAdapter<T> extends ArrayAdapter<T> {

New members for:
- the pre-filter normalisation method’s object
- our internal private class’ object
- the parent’s internal private class’ object (ArrayAdapter.mFilter is private, unfortunately)

    private MainActivity sParent;
private WtfArrayFilter sFilter;
private Filter pFilter;

The extended constructor (we need to implement only the one we’re actually calling):
- call the inherited constructor
- store away the parent’s mFilter value
- instantiate our own WtfArrayFilter
- store away the pre-filter normalisation method’s object, as WtfArrayFilter will need it later

    public WtfArrayAdapter(Context context, @LayoutRes int resource,
@NonNull T[] objects, MainActivity parent) {
super(context, resource, 0, Arrays.asList(objects));
sParent = parent;
sFilter = new WtfArrayFilter();
pFilter = super.getFilter();
}

Override ArrayAdapter’s getFilter() method to always return our new WtfArrayFilter:

    public Filter getFilter() {
return sFilter;
}

Implementation of WtfArrayFilter as Filter subclass just like ArrayFilter does, and forwarding all boring (i.e. unchanged) calls to the inherited ArrayFilter:

    private class WtfArrayFilter extends Filter {
protected void publishResults(CharSequence constraint,
FilterResults results) {
pFilter.publishResults(constraint, results);
}

We only need to change one method to do our own filtering before calling the inherited ArrayFilter’s filtering method:

        protected FilterResults performFiltering(CharSequence prefix) {
return pFilter.performFiltering(prefix == null ? null :
sParent.normaliseAcronym(prefix.toString()));
}
}
}

Improvements welcome, I’m still a Java™ beginner.

android filter custom array adapter and bring back old items again

My solution:

 public class ResultsArrayAdapter extends ArrayAdapter<SuchErgebnis> {

Context myContext;
int layoutResourceId;
ArrayList<SuchErgebnis> ergebnisListeOriginal = null;
ArrayList<SuchErgebnis> ergebnisListeGefiltert = null;
private Filter filter;

public ResultsArrayAdapter(Context context, int textViewResourceId,
ArrayList<SuchErgebnis> objects) {
super(context, textViewResourceId, objects);

this.myContext = context;
this.layoutResourceId = textViewResourceId;

this.ergebnisListeOriginal = new ArrayList<SuchErgebnis>(objects);
this.ergebnisListeGefiltert = new ArrayList<SuchErgebnis>(objects);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ErgebnisHolder eHolder = null;
SuchErgebnis ergebnis = ergebnisListeGefiltert.get(position);

if (row == null) // Wird zum ersten Mal gelanden...
{
LayoutInflater inflater = (LayoutInflater) myContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

row = inflater.inflate(R.layout.ergebnis_list_item, parent, false);

eHolder = new ErgebnisHolder();
eHolder.eTitel = (TextView) row
.findViewById(R.id.ergebnis_list_item_textview_titel);
eHolder.eInfo = (TextView) row
.findViewById(R.id.ergebnis_list_item_textview_info);
eHolder.eTreffer = (TextView) row
.findViewById(R.id.ergebnis_list_item_textview_treffer);

row.setTag(eHolder);
} else { // Wurde schoneinmal geladen, Views sind noch gespeichert...
eHolder = (ErgebnisHolder) row.getTag();
}

eHolder.eTitel.setText(ergebnis.titel);
eHolder.eInfo.setText(ergebnis.info != null ? ergebnis.info : "");
eHolder.eTreffer.setText(ergebnis.treffer != null ? ergebnis.treffer
: "");
row.setPadding(
ergebnis.isChild ? Main.mFHelper
.getPixels(10 * ergebnis.childNumber) : 0, 0, 0, 0);

return row;
}

@Override
public Filter getFilter() {
if (filter == null) {
filter = new ResultFilter();
}
return filter;
}

static class ErgebnisHolder {
TextView eTitel;
TextView eInfo;
TextView eTreffer;
}

@Override
public int getCount() {
return ergebnisListeGefiltert.size();
}

@Override
public SuchErgebnis getItem(int position) {
return ergebnisListeGefiltert.get(position);
}

private class ResultFilter extends Filter {

@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();

String filterText = constraint.toString().toLowerCase();
if (filterText == null || filterText.length() == 0) {
synchronized (this) {
results.values = ergebnisListeOriginal;
results.count = ergebnisListeOriginal.size();
}
} else {
ArrayList<SuchErgebnis> gefilterteListe = new ArrayList<SuchErgebnis>();
ArrayList<SuchErgebnis> ungefilterteListe = new ArrayList<SuchErgebnis>();
synchronized (this) {
ungefilterteListe.addAll(ergebnisListeOriginal);
}
for (int i = 0, l = ungefilterteListe.size(); i < l; i++) {
SuchErgebnis m = ungefilterteListe.get(i);
if (m.titel.toLowerCase().contains(filterText)) {
gefilterteListe.add(m);
}
}
results.values = gefilterteListe;
results.count = gefilterteListe.size();

}

return results;
}

@Override
protected void publishResults(CharSequence constraint,
FilterResults results) {

ergebnisListeGefiltert = (ArrayList<SuchErgebnis>) results.values;
if(results.count > 0)
{
notifyDataSetChanged();
}else{
notifyDataSetInvalidated();
}


}

}
}

Custom ArrayAdapter with filter is not filtering correctly

What you are missing currently is your adapter does not take into account anything about your filter, you set it to depend on the full species list with your get count, getItemPosition.

Also you should take care of updating your text when views are recycled by your adapter and not set the value only when views are created.

Something like that should be better :

class SearchAdapter(private val activity: Activity, private var species: ArrayList<Specie>) : ArrayAdapter<Specie>(activity, R.layout.specie_item, species) {

var filtered = ArrayList<Specie>()

override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
return convertView ?: createView(position, parent)
}

private fun createView(position: Int, parent: ViewGroup?): View {
val view = LayoutInflater.from(context).inflate(R.layout.specie_item, parent, false)
view?.name?.text = filtered[position].name
return view
}

override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View {
convertView ?: LayoutInflater.from(context).inflate(R.layout.specie_item, parent, false)
convertView?.name?.text = filtered[position].name
return super.getDropDownView(position, convertView, parent)
}

override fun getCount() = filtered.size

override fun getItem(position: Int) = filtered[position]

override fun getFilter() = filter

private var filter: Filter = object : Filter() {

override fun performFiltering(constraint: CharSequence?): Filter.FilterResults {
val results = FilterResults()

val query = if (constraint != null && constraint.isNotEmpty()) autocomplete(constraint.toString())
else arrayListOf()

results.values = query
results.count = query.size

return results
}

private fun autocomplete(input: String): ArrayList<Specie> {
val results = arrayListOf<Specie>()

for (specie in species) {
if (specie.name.toLowerCase().contains(input.toLowerCase())) results.add(specie)
}

return results
}

override fun publishResults(constraint: CharSequence?, results: Filter.FilterResults) {
filtered = results.values as ArrayList<Specie>
notifyDataSetInvalidated()
}

override fun convertResultToString(result: Any) = (result as Specie).name
}
}

Custom getFilter for Custom Adapter

You have to return the filter you created

return filter;

instead of

return super.getFilter();

Custom Filtering ArrayAdapter in ListView

Your problem are this lines:

this.original = items;
this.fitems = items;

Items is the list you use for your ListView and putting it in two different variables does not make two different lists out of it. You are only giving the list items two different names.

You can use:

this.fitems = new ArrayList(items);

that should generate a new List and changes on this list will only change the fitems list.



Related Topics



Leave a reply



Submit