SearchView to your ActionBar for Recyclerview in xamarin android?

↘锁芯ラ 提交于 2019-12-13 17:26:02

问题


How To Implement SearchView to your ActionBar Search menu icon for Recyclerview listitem in xamarin android?


回答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"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
   <item android: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:

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

        _recyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
        _adapter = new RecyclerViewAdapter(this,chemicals);
        _LayoutManager = new LinearLayoutManager(this);
        _recyclerView.SetLayoutManager(_LayoutManager);
        _recyclerView.SetAdapter(_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<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));

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

public class JavaHolder : Java.Lang.Object
{
    public readonly object Instance;

    public JavaHolder(object instance)
    {
        Instance = instance;
    }
}

public static class ObjectExtensions
{
    public static TObject ToNetObject<TObject>(this Java.Lang.Object value)
    {
        if (value == null)
            return default(TObject);

        if (!(value is JavaHolder))
            throw new 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;
    }

    public static Java.Lang.Object ToJavaObject<TObject>(this TObject value)
    {
        if (Equals(value, default(TObject)) && !typeof(TObject).IsValueType)
            return null;

        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:

public class RecyclerViewAdapter : RecyclerView.Adapter, IFilterable
{
    private List<Chemical> _originalData;
    private List<Chemical> _items;
    private readonly Activity _context;

    public Filter Filter { get; private set; }

    public RecyclerViewAdapter(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 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;
    }

    public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
    {
        ChemicalHolder vh = holder as ChemicalHolder;

        var chemical = _items[position];

        vh.Image.SetImageResource(chemical.DrawableId);
        vh.Caption.Text = chemical.Name;
    }

    public override int ItemCount
    {
        get { return _items.Count; }
    }

    public class ChemicalHolder : RecyclerView.ViewHolder
    {
        public ImageView Image { get; private set; }
        public TextView Caption { get; private set; }

        public ChemicalHolder(View itemView) : base(itemView)
        {
            Image = itemView.FindViewById<ImageView>(Resource.Id.chemImage);
            Caption = itemView.FindViewById<TextView>(Resource.Id.chemName);
        }
    }

    private class ChemicalFilter : Filter
    {
        private readonly RecyclerViewAdapter _adapter;
        public ChemicalFilter(RecyclerViewAdapter 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<Java.Lang.Object>()
                    .Select(r => r.ToNetObject<Chemical>()).ToList();

            _adapter.NotifyDataSetChanged();

            // Don't do this and see GREF counts rising
            constraint.Dispose();
            results.Dispose();
        }
    }
}


来源:https://stackoverflow.com/questions/45930524/searchview-to-your-actionbar-for-recyclerview-in-xamarin-android

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!