Mauro Piccotti
Mauro Piccotti

Reputation: 1847

Xamarin.Android: RecyclerView.Adapter, wrong recycling of elements

I have this selection activity I use to select elements. It's based on a superclass and it uses a RecyclerView. It's built using generics, so it's easy to make a "selector" passing a model class. It's possible to use it to make single or multiple selections.

The problem is that if I select on element and I scroll, sometimes I see also other rows selected. The behavior is strange, if I scroll down everything looks ok, if I return back enough to see again the selected rows and I restart to scroll down, I see other rows duplicated. It's only a graphical thing so if I press ok the activity returns only the right elements.

I'm quite sure it's a problem about the recycling, so I tried to set as not recyclable the selected rows with IsRecyclable, but it doesn't work.

Any suggestion?

enter image description here

Part of the RecyclerView.Adapter:

public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup viewGroup, int position)
{
    var itemView = LayoutInflater.From(viewGroup.Context).Inflate(Resource.Layout.activity_basemodel_item, viewGroup, false);
    var viewHolder = new BaseModelViewHolder(itemView, OnClick, OnLongClick, DetailActivityType);
    viewHolder.IsRecyclable = !SelectionEnabled;

    return viewHolder;
}

public void HighLight(RecyclerView.ViewHolder viewHolder, int position)
{
    if (!SelectionEnabled) { return; }

    var guid = ((BaseModelViewHolder)viewHolder).Model.Id;
    if (SelectedGuids.Contains(guid))
    {
        viewHolder.ItemView.Selected = true;
        viewHolder.ItemView.SetBackgroundColor(SelectedItemBackgroundColor);
    }
    else
    {
        viewHolder.ItemView.Selected = false;
    }
}

public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
{
    TModel model = DataSet[position];

    String title = model.Title;
    String subtitle = model.Subtitle;

    ((BaseModelViewHolder)viewHolder).TxtTitle.SetText(title, TextView.BufferType.Normal);
    if (!String.IsNullOrEmpty(subtitle) && !subtitle.Equals(title))
    {
        ((BaseModelViewHolder)viewHolder).TxtSubtitle.SetText(subtitle, TextView.BufferType.Normal);
    }
    ((BaseModelViewHolder)viewHolder).Model = model;

    this.HighLight(viewHolder, position);
}

Upvotes: 1

Views: 530

Answers (1)

Alexandre
Alexandre

Reputation: 589

RecyclerViews can show weird behavior when the Model is passed inside the ViewModel:

((BaseModelViewHolder)viewHolder).Model = model; //problem

To solve this, you can do it like this:

public void HighLight(RecyclerView.ViewHolder viewHolder, int position)
{
    if (!SelectionEnabled) { return; }

    TModel model = DataSet[position];
    var guid = model.Id;
    if (SelectedGuids.Contains(guid))
    {
        viewHolder.ItemView.SetBackgroundColor(SelectedItemBackgroundColor);
    }
    else
    {
        viewHolder.ItemView.SetBackgroundColor(DefaultItemBackgroundColor);
    }
}

public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
{
    TModel model = DataSet[position];

    String title = model.Title;
    String subtitle = model.Subtitle;

    ((BaseModelViewHolder)viewHolder).TxtTitle.SetText(title, TextView.BufferType.Normal);
    if (!String.IsNullOrEmpty(subtitle) && !subtitle.Equals(title))
    {
        ((BaseModelViewHolder)viewHolder).TxtSubtitle.SetText(subtitle, TextView.BufferType.Normal);
    }

    this.HighLight(viewHolder, position);

    // To highlight an item when clicked:
    viewHolder.ItemView.Click -= HighLight_Item;
    viewHolder.ItemView.Click += HighLight_Item; //This is to avoid subscribing the event everytime the view is shown
}

To select an item:

private void HighLight_Item(object sender, EventArgs e)
{
    //You need to pass the RecyclerView as an argument to the Adapter
    int position = this.recyclerView.GetChildAdapterPosition((View)sender);
    TModel model = DataSet[position];
    var guid = model.Id;
    //If already contains then remove, if doesn't contain then add
    if(SelectedGuids.Contains(guid)) SelectedGuids.Remove(guid);
    else SelectedGuids.Add(guid);
    //This will update the item view
    this.NotifyItemChanged(position); 
}

Upvotes: 1

Related Questions