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.
- Proper implementation of
ListTO
, although it has nothing to do with your goal right now - Implement custom filter
- 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
Dynamically Add and Remove View to Viewpager
Error:(6, 0) Gradle Dsl Method Not Found: 'Google()'
Android Service Stops When App Is Closed
Android Preventing Double Click on a Button
How to Set Image Button Backgroundimage for Different State
Problems with Android Fragment Back Stack
Android Popupwindow with Tooltip Arrow
How to Join Two SQLite Tables in My Android Application
Datepicker: How to Popup Datepicker When Click on Edittext
Image in Canvas with Touch Events
Android Canvas: Drawing Too Large Bitmap
How to Hide a Menu Item in the Actionbar