Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Monodroid - Handling Click events inside ListAdapter rows

I have a ListView with an ArrayAdapter set to it, and each row in the adapter contains four buttons that need to receive and handle click events. Normally in Android I would call SetOnClickListener on each button when I create the cell, but Mono gives the ability to set event handlers for the Click event instead. It looks like there's some weirdness to this in Mono though, because I run into one of two issues depending on where I set the event handler.

ArrayAdapter GetView Example 1:

View tweetCell = convertView;
if (tweetCell == null) {
    tweetCell = ((LayoutInflater)Context.GetSystemService (Context.LayoutInflaterService)).Inflate (Resource.Layout.TweetCell, null);

    tweetCell.FindViewById (Resource.Id.btn_moveTweet).Click += (object sender, EventArgs e) => MoveTweet (GetItem(position));

    tweetCell.FindViewById (Resource.Id.btn_unfavoriteTweet).Click += (object sender, EventArgs e) => UnfavoriteTweet (GetItem(position));

    tweetCell.FindViewById (Resource.Id.btn_hideTweet).Click += (object sender, EventArgs e) => HideTweet (GetItem(position));

    tweetCell.FindViewById (Resource.Id.btn_shareTweet).Click += (object sender, EventArgs e) => ShareTweet (GetItem(position));
}

Here, my event handler only gets set once per button (good!), but the position value is wrong most of the time. I'm wondering if the conversion from Mono to Android code is causing GetItem(position) to use the same value for position every time (the value that position is set to when the cell is first created). This code would work totally fine in normal Android.

ArrayAdapter GetView Example 2:

View tweetCell = convertView;
if (tweetCell == null) {
    tweetCell = ((LayoutInflater)Context.GetSystemService (Context.LayoutInflaterService)).Inflate (Resource.Layout.TweetCell, null);
}
tweetCell.FindViewById (Resource.Id.btn_moveTweet).Click += (object sender, EventArgs e) => MoveTweet (GetItem(position));

tweetCell.FindViewById (Resource.Id.btn_unfavoriteTweet).Click += (object sender, EventArgs e) => UnfavoriteTweet (GetItem(position));

tweetCell.FindViewById (Resource.Id.btn_hideTweet).Click += (object sender, EventArgs e) => HideTweet (GetItem(position));

tweetCell.FindViewById (Resource.Id.btn_shareTweet).Click += (object sender, EventArgs e) => ShareTweet (GetItem(position));

This method does cause the click event to be fired for the correct position, but it sets a new event handler every time the row is recycled. This causes click events for lots of rows at the same time. A workaround for this method would seem to be to keep references to the event handlers and remove them before setting them again inside GetView, but this seems extremely inelegant.

Is there a better method for handling click events inside ListView items in Monodroid?

like image 630
BigFwoosh Avatar asked Apr 25 '13 13:04

BigFwoosh


1 Answers

i know its an old thread, but it has a lot of votes and is still marked as unanswered

It is only logical that the scenarios that you described happens! It has nothing to do with mono whatsoever. It has to do with the convertview. This is what the documentation says on convertview:

convertView - The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view.

Example 1: In your first example the convertView will be null the first time and the events will be set. Then the next time the GetView method is called the convertview may or may not be null. If it is not null it will use the old view with the events still attached! So this means that the event with the position parameter are still from a previous view!

Example 2: This example will work as expected but it is not very efficient like you mentioned. It has to locate the controls every time the FindViewById method is called.

Solution: The solution for this performance problem is to implement the viewholder pattern.

First you create a class that will hold your views:

private class MyViewHolder : Java.Lang.Object 
{
  public Button MoveTweet { get; set; }
  public Button ShareTweet { get; set; }
  public Button UnfavoriteTweet { get; set; }
  public Button HideTweet { get; set; }
}

Now you can use this viewholder in your code

public override View GetView (int position, View convertView, ViewGroup parent)
{
  MyViewHolder holder;
  var view = convertView;

  if(view != null) 
    holder = view.Tag as MyViewHolder;


  if (holder == null) {
    holder = new MyViewHolder ();
    view = activity.LayoutInflater.Inflate (Resource.Layout.OptimizedItem, null);
    holder.MoveTweet = view.FindViewById<Button> (Resource.Id. btn_moveTweet);
    holder.ShareTweet = view.FindViewById<Button> (Resource.Id. btn_shareTweet);
    holder.UnfavoriteTweet = view.FindViewById<Button> (Resource.Id. btn_unfavoriteTweetTweet);
    holder.HideTweet = view.FindViewById<Button> (Resource.Id. btn_hideTweet);
    view.Tag = holder;
  } 


  holder.MoveTweet.Click += (object sender, EventArgs e) => MoveTweet (GetItem(position));
  holder.UnfavoriteTweet.Click += (object sender, EventArgs e) => UnfavoriteTweet (GetItem(position))
  holder.HideTweet.Click += (object sender, EventArgs e) => HideTweet (GetItem(position));
  holder.FavoriteTweet.Click += (object sender, EventArgs e) => ShareTweet (GetItem(position));

  return view;
}

For more information on this check out: https://blog.xamarin.com/creating-highly-performant-smooth-scrolling-android-listviews/

like image 191
Marc Bruins Avatar answered Oct 13 '22 09:10

Marc Bruins