Custom Filtering in Android Using Arrayadapter

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.

Custom filtering in Android using ArrayAdapter

This fixed my problem. I'm not certain it's the best solution, but it works. My project's open-source, so feel free to use any of the code here should it prove usefull :-).

package me.alxandr.android.mymir.adapters;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import me.alxandr.android.mymir.R;
import me.alxandr.android.mymir.model.Manga;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.SectionIndexer;
import android.widget.TextView;

public class MangaListAdapter extends ArrayAdapter<Manga> implements SectionIndexer
{
public ArrayList<Manga> items;
public ArrayList<Manga> filtered;
private Context context;
private HashMap<String, Integer> alphaIndexer;
private String[] sections = new String[0];
private Filter filter;
private boolean enableSections;

public Manga getByPosition(int position)
{
return items.get(position);
}

public MangaListAdapter(Context context, int textViewResourceId, ArrayList<Manga> items, boolean enableSections)
{
super(context, textViewResourceId, items);
this.filtered = items;
this.items = ArrayList<Manga> items.clone();
this.context = context;
this.filter = new MangaNameFilter();
this.enableSections = enableSections;

if(enableSections)
{
alphaIndexer = new HashMap<String, Integer>();
for(int i = items.size() - 1; i >= 0; i--)
{
Manga element = items.get(i);
String firstChar = element.getName().substring(0, 1).toUpperCase();
if(firstChar.charAt(0) > 'Z' || firstChar.charAt(0) < 'A')
firstChar = "@";

alphaIndexer.put(firstChar, i);
}

Set<String> keys = alphaIndexer.keySet();
Iterator<String> it = keys.iterator();
ArrayList<String> keyList = new ArrayList<String>();
while(it.hasNext())
keyList.add(it.next());

Collections.sort(keyList);
sections = new String[keyList.size()];
keyList.toArray(sections);
}
}

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
if(v == null)
{
LayoutInflater vi = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.mangarow, null);
}

Manga o = filtered.get(position);
if(o != null)
{
TextView tt = (TextView) v.findViewById(R.id.MangaRow_MangaName);
TextView bt = (TextView) v.findViewById(R.id.MangaRow_MangaExtra);
if(tt != null)
tt.setText(o.getName());
if(bt != null)
bt.setText(o.getLastUpdated() + " - " + o.getLatestChapter());

if(enableSections && getSectionForPosition(position) != getSectionForPosition(position + 1))
{
TextView h = (TextView) v.findViewById(R.id.MangaRow_Header);
h.setText(sections[getSectionForPosition(position)]);
h.setVisibility(View.VISIBLE);
}
else
{
TextView h = (TextView) v.findViewById(R.id.MangaRow_Header);
h.setVisibility(View.GONE);
}
}

return v;
}

@Override
public void notifyDataSetInvalidated()
{
if(enableSections)
{
for (int i = items.size() - 1; i >= 0; i--)
{
Manga element = items.get(i);
String firstChar = element.getName().substring(0, 1).toUpperCase();
if(firstChar.charAt(0) > 'Z' || firstChar.charAt(0) < 'A')
firstChar = "@";
alphaIndexer.put(firstChar, i);
}

Set<String> keys = alphaIndexer.keySet();
Iterator<String> it = keys.iterator();
ArrayList<String> keyList = new ArrayList<String>();
while (it.hasNext())
{
keyList.add(it.next());
}

Collections.sort(keyList);
sections = new String[keyList.size()];
keyList.toArray(sections);

super.notifyDataSetInvalidated();
}
}

public int getPositionForSection(int section)
{
if(!enableSections) return 0;
String letter = sections[section];

return alphaIndexer.get(letter);
}

public int getSectionForPosition(int position)
{
if(!enableSections) return 0;
int prevIndex = 0;
for(int i = 0; i < sections.length; i++)
{
if(getPositionForSection(i) > position && prevIndex <= position)
{
prevIndex = i;
break;
}
prevIndex = i;
}
return prevIndex;
}

public Object[] getSections()
{
return sections;
}

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

private class MangaNameFilter extends Filter
{

@Override
protected FilterResults performFiltering(CharSequence constraint) {
// NOTE: this function is *always* called from a background thread, and
// not the UI thread.
constraint = constraint.toString().toLowerCase();
FilterResults result = new FilterResults();
if(constraint != null && constraint.toString().length() > 0)
{
ArrayList<Manga> filt = new ArrayList<Manga>();
ArrayList<Manga> lItems = new ArrayList<Manga>();
synchronized (this)
{
lItems.addAll(items);
}
for(int i = 0, l = lItems.size(); i < l; i++)
{
Manga m = lItems.get(i);
if(m.getName().toLowerCase().contains(constraint))
filt.add(m);
}
result.count = filt.size();
result.values = filt;
}
else
{
synchronized(this)
{
result.values = items;
result.count = items.size();
}
}
return result;
}

@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
// NOTE: this function is *always* called from the UI thread.
filtered = (ArrayList<Manga>)results.values;
notifyDataSetChanged();
clear();
for(int i = 0, l = filtered.size(); i < l; i++)
add(filtered.get(i));
notifyDataSetInvalidated();
}

}
}

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;
}

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
}
}

Filtering custom ListView with multiple TextViews using a Filter on an ArrayAdapter T implements Parcelable

In your publishResults method:

        @Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}

You're not setting the filtered results on your adapter as the new dataset to display before invaliding the dataset. Also, you should be keeping a copy of your original values as well, so if they empty out the query you can retain and reset the original results.

Edit for Framework example code:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/widget/ArrayAdapter.java#ArrayAdapter.ArrayFilter

Filter custom ArrayAdapter or implement search in activity

The SimpleAdapter already comes enabled with filtering capabilities. You'll just need to invoke something like:

getListAdapter().getFilter().filter("Text to search for");

That's it. Android will handle filtering through your data and displaying those that match the given string.

Update - Custom Filter

To customize how the filter searches is a lot more involved. The short answer, you'll need to write your own version of the SimpleAdapter. AFAIK, none of the Android provided adapters provide an easy solution for how to customize the filtering logic. It's why I am building up Advanced-Adapters, unfortunately I haven't written a solution for the SimpleAdapter yet. It's all ArrayAdapter and SparseArray alternatives right now.

You'll probably find lots of people and examples saying to extend the SimpleAdapter class and override the getFilter() method to have it return a new Filter class implemented by you. You can then use your mCommentList arraylist to figure out how to filter the data. While this will work in some situations, it's the incorrect approach and will fail for various cases.

The correct solution is to create your own adapter class that behaves like SimpleAdapter but extends the BaseAdapter class...which basically means create your own adapter from scratch. Being new to Android, it can be a very daunting task and there's a lot to learn and understand on how adapters work.

The easiest way to do this would be to literally copy the SimpleAdapter source code into your project and then manually change the filter code's logic to how you want it. The logic to adjust specifically occurs within this for loop. Keep in mind, that's within the performFiltering() method which is on a background thread! Hence all the synchronized blocks.

Custom filter for ArrayAdapter in GridView

this is how I solved my problem.
Since my filter is pretty basic I did my own loop and filtering and then passed all of this to adapter.

private void doMySearch(final String query){
list = (GridView)findViewById(R.id.apps_list);


final List<AppDetail> apps_filtered = new ArrayList<>();

for(int q = 0; q < apps.size(); q++){
if(apps.get(q).label.toString().toLowerCase().startsWith(query)) {
Log.e("ddd", apps.get(q).label.toString().toLowerCase());

AppDetail app = new AppDetail();
app.label = apps.get(q).label;
app.name = apps.get(q).name;
app.icon = apps.get(q).icon;
apps_filtered.add(app);
}
}

ArrayAdapter<AppDetail> adapter = new ArrayAdapter<AppDetail>(this, R.layout.list_item, apps_filtered) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = getLayoutInflater().inflate(R.layout.list_item, null);
}

ImageView appIcon = (ImageView)convertView.findViewById(R.id.item_app_icon);
appIcon.setImageDrawable(apps_filtered.get(position).icon);
appIcon.setTag(apps_filtered.get(position).name);
TextView appLabel = (TextView)convertView.findViewById(R.id.item_app_label);
appLabel.setText(apps_filtered.get(position).label);
//TextView appName = (TextView)convertView.findViewById(R.id.item_app_name);
//appName.setText(apps_filtered.get(position).name);

return convertView;
}
};

list.setAdapter(adapter);
}


Related Topics



Leave a reply



Submit