Skip to content

Instantly share code, notes, and snippets.

@Cheesebaron
Last active March 15, 2023 19:30
Show Gist options
  • Save Cheesebaron/9838325 to your computer and use it in GitHub Desktop.
Save Cheesebaron/9838325 to your computer and use it in GitHub Desktop.
Custom Adapter with Filter
namespace SearchViewSample
{
public class Chemical
{
public string Name { get; set; }
public int DrawableId { get; set; }
}
}
using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Views;
using Android.Widget;
using Java.Lang;
using Object = Java.Lang.Object;
namespace SearchViewSample
{
public class ChemicalsAdapter : BaseAdapter<Chemical>, IFilterable
{
private List<Chemical> _originalData;
private List<Chemical> _items;
private readonly Activity _context;
public ChemicalsAdapter(Activity activity, IEnumerable<Chemical> chemicals)
{
_items = chemicals.OrderBy(s => s.Name).ToList();
_context = activity;
Filter = new ChemicalFilter(this);
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var view = convertView ?? _context.LayoutInflater.Inflate(Resource.Layout.Chemical, null);
var chemical = _items[position];
var nameView = view.FindViewById<TextView>(Resource.Id.chemName);
var imageView = view.FindViewById<ImageView>(Resource.Id.chemImage);
nameView.Text = chemical.Name;
imageView.SetImageResource(chemical.DrawableId);
return view;
}
public override int Count
{
get { return _items.Count; }
}
public override Chemical this[int position]
{
get { return _items[position]; }
}
public Filter Filter { get; private set; }
public override void NotifyDataSetChanged()
{
// If you are using cool stuff like sections
// remember to update the indices here!
base.NotifyDataSetChanged();
}
private class ChemicalFilter : Filter
{
private readonly ChemicalsAdapter _adapter;
public ChemicalFilter(ChemicalsAdapter adapter)
{
_adapter = adapter;
}
protected override FilterResults PerformFiltering(ICharSequence constraint)
{
var returnObj = new FilterResults();
var results = new List<Chemical>();
if (_adapter._originalData == null)
_adapter._originalData = _adapter._items;
if (constraint == null) return returnObj;
if (_adapter._originalData != null && _adapter._originalData.Any())
{
// Compare constraint to all names lowercased.
// It they are contained they are added to results.
results.AddRange(
_adapter._originalData.Where(
chemical => chemical.Name.ToLower().Contains(constraint.ToString())));
}
// Nasty piece of .NET to Java wrapping, be careful with this!
returnObj.Values = FromArray(results.Select(r => r.ToJavaObject()).ToArray());
returnObj.Count = results.Count;
constraint.Dispose();
return returnObj;
}
protected override void PublishResults(ICharSequence constraint, FilterResults results)
{
using (var values = results.Values)
_adapter._items = values.ToArray<Object>()
.Select(r => r.ToNetObject<Chemical>()).ToList();
_adapter.NotifyDataSetChanged();
// Don't do this and see GREF counts rising
constraint.Dispose();
results.Dispose();
}
}
}
}
using System.Collections.Generic;
using Android.App;
using Android.Runtime;
using Android.Support.V4.View;
using Android.Support.V7.App;
using Android.Support.V7.Widget;
using Android.Views;
using Android.Widget;
using Android.OS;
namespace SearchViewSample
{
[Activity(Label = "SearchView Sample", MainLauncher = true, Icon = "@drawable/icon",
Theme = "@style/Theme.AppCompat.Light")]
public class SearchViewActivity : ActionBarActivity
{
private SearchView _searchView;
private ListView _listView;
private ChemicalsAdapter _adapter;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
SupportActionBar.SetDisplayShowHomeEnabled(true);
var chemicals = new List<Chemical>
{
new Chemical {Name = "Niacin", DrawableId = Resource.Drawable.Icon},
new Chemical {Name = "Biotin", DrawableId = Resource.Drawable.Icon},
new Chemical {Name = "Chromichlorid", DrawableId = Resource.Drawable.Icon},
new Chemical {Name = "Natriumselenit", DrawableId = Resource.Drawable.Icon},
new Chemical {Name = "Manganosulfate", DrawableId = Resource.Drawable.Icon},
new Chemical {Name = "Natriummolybdate", DrawableId = Resource.Drawable.Icon},
new Chemical {Name = "Ergocalciferol", DrawableId = Resource.Drawable.Icon},
new Chemical {Name = "Cyanocobalamin", DrawableId = Resource.Drawable.Icon},
};
_listView = FindViewById<ListView>(Resource.Id.listView);
_adapter = new ChemicalsAdapter(this, chemicals);
_listView.Adapter = _adapter;
}
public override bool OnCreateOptionsMenu(IMenu menu)
{
MenuInflater.Inflate(Resource.Menu.main, menu);
var item = menu.FindItem(Resource.Id.action_search);
var searchView = MenuItemCompat.GetActionView(item);
_searchView = searchView.JavaCast<SearchView>();
_searchView.QueryTextChange += (s, e) => _adapter.Filter.InvokeFilter(e.NewText);
_searchView.QueryTextSubmit += (s, e) =>
{
// Handle enter/search button on keyboard here
Toast.MakeText(this, "Searched for: " + e.Query, ToastLength.Short).Show();
e.Handled = true;
};
MenuItemCompat.SetOnActionExpandListener(item, new SearchViewExpandListener(_adapter));
return true;
}
private class SearchViewExpandListener
: Java.Lang.Object, MenuItemCompat.IOnActionExpandListener
{
private readonly IFilterable _adapter;
public SearchViewExpandListener(IFilterable adapter)
{
_adapter = adapter;
}
public bool OnMenuItemActionCollapse(IMenuItem item)
{
_adapter.Filter.InvokeFilter("");
return true;
}
public bool OnMenuItemActionExpand(IMenuItem item)
{
return true;
}
}
}
}
@hunii
Copy link

hunii commented Dec 20, 2017

Hey, I have implemented sort of same thing on Xamarin Forms with custom adapter renderer on Android and I noticed GetView is called triple times. Position iterated 0 to suggestionList.count then goes back to 0 and iterate three times. Did you notice that by anychance when you tested this on Android?

@Akid95
Copy link

Akid95 commented Jan 29, 2018

Hi,

we implemented your solution to filter a custom adapter for a listview but the problem is that we are using the SimpleListItemMultipleChoice layout and the checkboxes don't get filtered. Better explained here:
https://forums.xamarin.com/discussion/113722/multichoice-singlechoice-custom-filter-listview

does anybody know how to approach a solution for that? My idea is to get the id or the position of the filtered elements and look in the original list wether they are selected or not and update the results list accordingly but i don't see a way of identifiying the elements.
Thanks for the piece of code, works great.

@soupjake
Copy link

If people don't want to use the "nasty object wrapping", you can avoid it if you let your class extend Java.Lang.Object itself like:
public class Chemical : Java.Lang.Object

You'd then change the relevant parts in your FilterResults and PublishResults methods:
returnObj.Values = results.ToArray();

_adapter._items = new List<Chemical>(values.ToArray<Chemical>());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment