2

I'm having a trouble with both ListView and RecyclerView

Initially, I created a ListView, everything is fine. Then I set onClick event for it so that every time I click an item, it changes its color to yellow. The OnClick function I wrote in the MainActivity. Problem is that when I test, not only that item changes its color but 2 items change. I read that it's because I reuse the view.

So I switch my tactics, using RecyclerView instead but same problem occurs. When I click one item to change its color, another below also changes. I guess it's because both ListView and RecyclerView reuse those Item so they confuse when I click one.

I don't know how to solve this problem, I found a solution is to add an array of boolean which marks which item is clicked but it doesn't work. Any idea guys?

So here is the code MainActivity

class MainActivity : Activity
    {
        public RecyclerView recyclerView;
        public RecyclerView.LayoutManager manager;
        public RecyclerView.Adapter adapter;
        List<Row> lst;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);
            init();
            recyclerView = (RecyclerView)FindViewById(Resource.Id.recyclerView);
            manager = new LinearLayoutManager(this);
            recyclerView.SetLayoutManager(manager);
            CustomAdapter adapter = new CustomAdapter(lst, this);
            adapter.ItemClick += onItemClick;
            recyclerView.SetAdapter(adapter);
        }

        public void init()
        {
            lst = new List<Row>();
            for (int i = 0; i < 15; i++)
            {
                Row row = new Row() { field1="1:43:00", field2="09-Apr-16", field3="KPI/Overflow", field4="Kevin Bacon", field5="Unowned", field6= "People Counting @ IPCAM-ID-C-1-1" };
                lst.Add(row);
            }
        }
        public void onItemClick(object sender, int position)
        {
            int itemPos = position + 1;
            //Toast.MakeText(this, "this is " + itemPos, ToastLength.Short).Show();
            recyclerView.GetChildAt(position).SetBackgroundColor(Android.Graphics.Color.Green);
        }
    }

Custom adapter

public class CustomAdapter : RecyclerView.Adapter
    {
        public Activity _activity;
        public List<Row> lst;
        public event EventHandler<int> ItemClick;

        public CustomAdapter(List<Row> lst, Activity activity)
        {
            this.lst = lst;
            this._activity = activity;
        }

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

        public void OnClick(int position)
        {
            if (ItemClick!=null)
            {
                ItemClick(this, position);
            }
        }

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

            myholder.textView1.Text = lst[position].field1;
            myholder.textView2.Text = lst[position].field2;
            myholder.textView3.Text = lst[position].field3;
            myholder.textView4.Text = lst[position].field4;
            myholder.textView5.Text = lst[position].field5;
            myholder.textView6.Text = lst[position].field6;


        }

        public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
        {
            View v = this._activity.LayoutInflater.Inflate(Resource.Layout.item, parent, false);

            TextView tv1 = (TextView)v.FindViewById(Resource.Id.textView1);
            TextView tv2 = (TextView)v.FindViewById(Resource.Id.textView2);
            TextView tv3 = (TextView)v.FindViewById(Resource.Id.textView3);
            TextView tv4 = (TextView)v.FindViewById(Resource.Id.textView4);
            TextView tv5 = (TextView)v.FindViewById(Resource.Id.textView5);
            TextView tv6 = (TextView)v.FindViewById(Resource.Id.textView6);

            MyViewHolder holder = new MyViewHolder(v, OnClick) { textView1 = tv1, textView2 = tv2, textView3 = tv3, textView4 = tv4, textView5 = tv5, textView6 = tv6 };
            return holder;
        }
    }

    class MyViewHolder : RecyclerView.ViewHolder
    {
        public TextView textView1, textView2, textView3, textView4, textView5, textView6;
        public View mainView;

        public MyViewHolder(View view, Action<int> listener) : base(view)
        {
            mainView = view;
            mainView.Click += (sender, e) => listener(base.Position);
        }
    }

I followed the example for the OnClick handler on Xamarin site https://developer.xamarin.com/guides/android/user_interface/recyclerview/

Pooya
  • 6,083
  • 3
  • 23
  • 43
LaXuanLinh
  • 47
  • 1
  • 9

3 Answers3

2

Your issue is with your code. You send the correct position to your event handler, but then you increment it by one in the Activity. Both ends should be using the 0-based index of the item position. There is no need to increment by one.

For changing the background color of the selected item, you can use a selector in XML so you wouldn't even need to do this in code.

Here is an example.

row_selector.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:color="@android:color/green" />
    <item android:state_selected="false" android:color="@android:color/transparent"/>
</selector>

row_content.axml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/row_layout_parent"
    android:background="@drawable/row_selector">

    <!-- your row content -->

</LinearLayout>

Then your view holder would be updated to this...

class MyViewHolder : RecyclerView.ViewHolder
{
    public TextView textView1, textView2, textView3, textView4, textView5, textView6;
    public View mainView;
    private LinearLayout _layoutParent;

    public MyViewHolder(View view, Action<int> listener) : base(view)
    {
        mainView = view;
        _layoutParent = mainView.FindViewById<LinearLayout>(Resource.Id.row_layout_parent);
        _layoutParent.Click += (sender, e) => _layoutParent.Selected = true;
    }
}

I removed the other click event. If you still need it for other reasons, then you can add it back, but it's not necessary for just setting the item background color when selected.

Ryan Alford
  • 7,514
  • 6
  • 42
  • 56
  • thank you for your reply, I have never heard of XML selector before so all I came with is click event handler. Actually I need to change the item view when it's clicked, I haven't found how to do it, I tried setBackgroundResource and Inflater in handler but didn't work. So I think just test the Item click event first by changing its color every time I click. Do you have any example about using selector to change item view when it's clicked? – LaXuanLinh Apr 10 '16 at 17:16
  • My code above should work. The `row_selector.xml` file should be in the `drawable` directory. – Ryan Alford Apr 11 '16 at 03:03
0

For Listview you should set choiceMode as below.

listView.ChoiceMode = ChoiceMode.Single;

Hope it help you :)-

Veswanth
  • 1,061
  • 9
  • 22
0

Create a reusable recycleview adapter GENERIC

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Support.V7.App;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Text.Style;
using Android.Util;
using Android.Views;
using Android.Widget;
using Java.Util.Zip;
using ActionMenuView = Android.Support.V7.Widget.ActionMenuView;

namespace Android.Basic.Core
{
    public class GenericRecyclerViewAdapter<T> : RecyclerView.Adapter
    {
        /// <summary>
        /// You can set this for different custom cardview
        /// </summary>
        private int CardViewResourceLayout { get; set; }
        public ObservableCollection<T> Items { get; private set; }
        public  event EventHandler<RecyclerViewViewHolder> ItemViewTemplated;
        public RecyclerView.LayoutManager layoutManager;
        public GenericRecyclerViewAdapter(RecyclerView recyclerView, IEnumerable<T> items, int cardViewResourceLayout, bool isList = true, bool isVertical = true) : base()
        {
            
            if(isList)
            {
                var vertical = isVertical ? LinearLayoutManager.Vertical : LinearLayoutManager.Horizontal;
                layoutManager = new LinearLayoutManager(recyclerView.Context, vertical, false);
            }
            else
            {
                var vertical = isVertical ? GridLayoutManager.Vertical : GridLayoutManager.Horizontal;
                layoutManager = new GridLayoutManager(recyclerView.Context, 3, vertical, false);
            }
            recyclerView.SetLayoutManager(layoutManager);
            this.Items = new ObservableCollection<T>(items);
            this.CardViewResourceLayout = cardViewResourceLayout;
            this.Items.CollectionChanged += delegate
            {
                this.NotifyDataSetChanged();
            };
           
        }
        
        public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
        {
            var itemView = LayoutInflater.From(parent.Context).Inflate(CardViewResourceLayout, parent, false);
#if DEBUG
            Log.Info("GenericRecyclerViewAdapter - ", CardViewResourceLayout.ToString());
#endif
            RecyclerViewViewHolder vh = new RecyclerViewViewHolder(itemView);
            return vh;
        }        
        
        public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
        {
            RecyclerViewViewHolder vh = holder as RecyclerViewViewHolder;
            vh.ItemPosition = position;
            vh.TemplateView.Tag = position;
            vh.TemplateView.Click -= TemplateView_Click;
            vh.TemplateView.Click += TemplateView_Click;
            ItemViewTemplated?.Invoke(this, vh);
        }
        public event EventHandler<T> ItemClicked;
     
        private void TemplateView_Click(object sender, EventArgs e)
        {
            var position = (int)((View)sender).Tag;
            this.ItemClicked?.Invoke(sender, this.Items[position]);
          
        }

        public override int ItemCount
        {
            get { return this.Items.Count; }
        }
        public override long GetItemId(int position)
        {
            return base.GetItemId(position);
        }

    }

    public class RecyclerViewViewHolder : RecyclerView.ViewHolder, View.IOnCreateContextMenuListener,
        IMenuItemOnMenuItemClickListener
    {
        public View TemplateView { get; private set; }
        public int ItemPosition { get;  set; }
        public event EventHandler<MenuInfo> ContextMenuCreated;
        public event EventHandler<object> MenuItemClicked;
        public MenuInfo MenuInfo { get; private set; }
        public object Data { get; set; }
        public RecyclerViewViewHolder(View itemView) : base(itemView)
        {
            // Locate and cache view references:
            this.TemplateView = itemView;
            this.TemplateView.SetOnCreateContextMenuListener(this);
           
        }

        public void OnCreateContextMenu(IContextMenu menu, View v, IContextMenuContextMenuInfo menuInfo)
        {
            MenuInfo = new MenuInfo(menu, v, menuInfo);
            ContextMenuCreated?.Invoke(this, MenuInfo);

        }
        private Android.Views.MenuInflater menuInflater = null;
        /// <summary>
        /// After ContextMenuCreated 
        /// </summary>
        /// <param name="resourcemenu"></param>
        public void InflateMenu(int resourcemenu, SpannableString titleColor = null, object dta = null)
        {
            if (dta != null)
                this.Data = dta;
            if (this.TemplateView.Context is AppCompatActivity activity)
            {
                menuInflater = activity.MenuInflater;

            }
            else if (this.TemplateView.Context is Activity activity2)
            {
                menuInflater = activity2.MenuInflater;
            }
            var contextMenu = this.MenuInfo.ContextMenu;
            contextMenu.Clear();
            menuInflater.Inflate(resourcemenu, contextMenu);
            var num = contextMenu.Size() - 1;
            for (int i = 0; i <= num; i++)
            {
                var men = contextMenu.GetItem(i);
                if(titleColor != null)
                {
                    if (i == 0)
                    {                       
                        men.SetTitle(titleColor);
                        men.SetChecked(true);
                    }
                }
                if (i != 0)
                {
                    men.SetOnMenuItemClickListener(this);
                }

            }
        }
        
        public bool OnMenuItemClick(IMenuItem item)
        {
            this.MenuItemClicked?.Invoke(item, this.Data);
            return true;
        }

        public float PosX;
        public float PosY;
        
    }
    
    public class MenuInfo
    {
        public IContextMenu ContextMenu { get; }
        public View View { get; }
        public IContextMenuContextMenuInfo ContextMenuInfo { get; }
        public MenuInfo(IContextMenu contextMenu, View view, IContextMenuContextMenuInfo menuInfo)
        {
            this.ContextMenu = contextMenu;
            this.View = view;
            this.ContextMenuInfo = menuInfo;
        }
    }
}

Usage

  RecyclerView recyclerView = new RecyclerView(this);
                    var viewAdapter = new Android.Basic.Core.GenericRecyclerViewAdapter<Java.IO.File>(recyclerView, files, Resource.Layout.directory_item);
                    var indiColor = ThemeHelper.IsDark ? ColorHelper.GetRandomLightColor() : ColorHelper.GetRandomDarkColor();
                    viewAdapter.ItemViewTemplated += (dd, holder) =>
                    {
                        var file = files[holder.ItemPosition];
                        var view = holder.ItemView;
                        var expanded = view.FindViewById<ExpandedView>(Resource.Id.expandedView);
                        expanded.SetToggleColor(indiColor);
                        expanded.SetTitle(file.Name);
                        GenerateRecycler(expanded, file);
                        
                    };
                    recyclerView.SetAdapter(viewAdapter);
                    expandedView.AddExpandedView(recyclerView);
Minute V
  • 25
  • 3