Skip to content Skip to sidebar Skip to footer

Searchview To Your Actionbar For Recyclerview In Xamarin Android?

How To Implement SearchView to your ActionBar Search menu icon for Recyclerview listitem 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.

  1. 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;
    }
}
  1. 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 Adapter 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.

  1. 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?"