Searchview To Your Actionbar For Recyclerview In Xamarin Android?
Solution 1:
I wrote up a simple demo about how to implement this feature, effect like this. You can see it in this GitHub Repository.
- Setting up the SearchView
In the folder res/menu
create a new file called main.xml
. In it add an item and set the actionViewClass
to android.support.v7.widget.SearchView
. Since you are using the support library you have to use the namespace of the support library to set the actionViewClass
attribute. Your main.xml
file should look something like this:
<?xml version="1.0" encoding="utf-8"?><menuxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><itemandroid:id="@+id/action_search"android:title="Search"android:icon="@android:drawable/ic_menu_search"app:showAsAction="always|collapseActionView"app:actionViewClass="android.support.v7.widget.SearchView" /></menu>
In your Activity
you have to inflate this menu xml like usual, then you can look for the MenuItem
which contains the SearchView
and add a delegate on QueryTextChange
which we are going to use to listen for changes to the text entered into the SearchView
:
protectedoverridevoidOnCreate(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},
};
_recyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
_adapter = new RecyclerViewAdapter(this,chemicals);
_LayoutManager = new LinearLayoutManager(this);
_recyclerView.SetLayoutManager(_LayoutManager);
_recyclerView.SetAdapter(_adapter);//
}
publicoverrideboolOnCreateOptionsMenu(IMenu menu)
{
MenuInflater.Inflate(Resource.Menu.main, menu);
var item = menu.FindItem(Resource.Id.action_search);
var searchView = MenuItemCompat.GetActionView(item);
_searchView = searchView.JavaCast<Android.Support.V7.Widget.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));
returntrue;
}
privateclassSearchViewExpandListener : Java.Lang.Object, MenuItemCompat.IOnActionExpandListener
{
privatereadonly IFilterable _adapter;
publicSearchViewExpandListener(IFilterable adapter)
{
_adapter = adapter;
}
publicboolOnMenuItemActionCollapse(IMenuItem item)
{
_adapter.Filter.InvokeFilter("");
returntrue;
}
publicboolOnMenuItemActionExpand(IMenuItem item)
{
returntrue;
}
}
- To wrap up .NET types with a Java type
You have to implement your own Filter
, as the nature of a custom Adapter is that you present custom stuff. Hence the default Filter
implementation cannot know how to filter that.
As Cheesebaron said, FilterResult
which is used to store the filtered values temporarily, expects that the object stored is a Java type. So either your model which you populate your Adapte
r with has to implement Java.Lang.Object
or you will have to wrap your values. I will show you the latter, as it will apply to the more use cases, as you probably cannot and probably do not want to implement Java.Lang.Object
in your contracts or whatever you are using to store data in, especially when you are communicating and sharing code between platforms.
To wrap up .NET
types with a Java type I am using modified code from this monodroid mailing list thread, which looks like this:
publicclassJavaHolder : Java.Lang.Object
{
publicreadonlyobject Instance;
publicJavaHolder(object instance)
{
Instance = instance;
}
}
publicstaticclassObjectExtensions
{
publicstatic TObject ToNetObject<TObject>(this Java.Lang.Object value)
{
if (value == null)
returndefault(TObject);
if (!(valueis JavaHolder))
thrownew InvalidOperationException("Unable to convert to .NET object. Only Java.Lang.Object created with .ToJavaObject() can be converted.");
TObject returnVal;
try { returnVal = (TObject)((JavaHolder)value).Instance; }
finally { value.Dispose(); }
return returnVal;
}
publicstatic Java.Lang.Object ToJavaObject<TObject>(this TObject value)
{
if (Equals(value, default(TObject)) && !typeof(TObject).IsValueType)
returnnull;
var holder = new JavaHolder(value);
return holder;
}
}
The main difference is that the Java holder object is disposed of immediately when it is converted to a .NET
object. Also the comparison in the original source, whether the value was null is unsafe, as it does not take value types into consideration. The object disposal is very useful to keep GREF
references down to a minimum. I really don't want the app to run high on memory.
- Filtering in custom Adapters
The Adapter
looks like this:
publicclassRecyclerViewAdapter : RecyclerView.Adapter, IFilterable
{
private List<Chemical> _originalData;
private List<Chemical> _items;
privatereadonly Activity _context;
public Filter Filter { get; privateset; }
publicRecyclerViewAdapter(Activity activity, IEnumerable<Chemical> chemicals)
{
_items = chemicals.OrderBy(s => s.Name).ToList();
_context = activity;
Filter = new ChemicalFilter(this);
}
publicoverridelongGetItemId(int position)
{
return position;
}
publicoverride RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.Chemical, parent, false);
ChemicalHolder vh = new ChemicalHolder(itemView);
return vh;
}
publicoverridevoidOnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
ChemicalHolder vh = holder as ChemicalHolder;
var chemical = _items[position];
vh.Image.SetImageResource(chemical.DrawableId);
vh.Caption.Text = chemical.Name;
}
publicoverrideint ItemCount
{
get { return _items.Count; }
}
publicclassChemicalHolder : RecyclerView.ViewHolder
{
public ImageView Image { get; privateset; }
public TextView Caption { get; privateset; }
publicChemicalHolder(View itemView) : base(itemView)
{
Image = itemView.FindViewById<ImageView>(Resource.Id.chemImage);
Caption = itemView.FindViewById<TextView>(Resource.Id.chemName);
}
}
privateclassChemicalFilter : Filter
{
privatereadonly RecyclerViewAdapter _adapter;
publicChemicalFilter(RecyclerViewAdapter adapter)
{
_adapter = adapter;
}
protectedoverride 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;
}
protectedoverridevoidPublishResults(ICharSequence constraint, FilterResults results)
{
using (var values = results.Values)
_adapter._items = values.ToArray<Java.Lang.Object>()
.Select(r => r.ToNetObject<Chemical>()).ToList();
_adapter.NotifyDataSetChanged();
// Don't do this and see GREF counts rising
constraint.Dispose();
results.Dispose();
}
}
}
Post a Comment for "Searchview To Your Actionbar For Recyclerview In Xamarin Android?"