April 22nd, 2014

Tips & Tricks for Highly Performing Android ListViews

James Montemagno
Principal Manager, Tech PM

I have been working with Xamarin.Android for a long time now and one question I get asked quiet often is how to optimize the Android ListView. Xamarin apps are native apps, which means that out of the box your app will take advantage of all of the optimizations that come with these views in the framework; however, when Google implemented the ListView they left out a few additional optimizations that developers can take advantage of to ensure a great user experience. Let’s first look at how the ListView and their adapters actually work:

Cell Reuse in Android ListView Adapters

What we see here is that Android is optimized for cell reuse. It will never take the number of cells that are displayed and cycle through them, so as your user scrolls through the list and a cell goes off the screen it is put into a recycle bin and then put back on top to be reused again. Let’s look at the standard code out of the box when creating an adapter and then optimize it:

First I will create a simple item for my adapter. It consists of two TextViews and one ImageView.

A simple android list adapter cell

BaseAdapter

For this example I have created a new Adapter and inherited from BaseAdapter. When I implement the GetView method, I could simply inflate the view, find the controls, and set the properties on them:

public override View GetView (int position, View convertView, ViewGroup parent)
{
  var view = activity.LayoutInflater.Inflate (Resource.Layout.OptimizedItem, null);
  var textView1 = view.FindViewById<TextView> (Resource.Id.textView1);
  var textView2 = view.FindViewById<TextView> (Resource.Id.textView2);
  var imageView = view.FindViewById<ImageView> (Resource.Id.imageView);

  textView1.Text = Names [position];
  textView2.Text = Descriptions [position];
  imageView.SetImageResource (Names [position].ToLower().Contains ("xamarin") ?
				   Resource.Drawable.hexagongreen :
				   Resource.Drawable.hexagopurple);

  return view;
}

There are a few issues with this implementation, which is that we are inflating the view and locating the controls each time GetView gets called. So let’s fix that.

Take Advantage of ConvertView

The first optimization we will want to make is to take advantage of the convertView that is passed into this method. In the official documentation for GetView it tells us exactly what the convertView is: “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.”

This means that if the convertView is null then we should inflate the view else we can simply fine the controls on view and set them.

public override View GetView (int position, View convertView, ViewGroup parent)
{
  var view = convertView;
  if(view == null) {
    view = activity.LayoutInflater.Inflate (Resource.Layout.OptimizedItem, null);
  }
  var textView1 = view.FindViewById<TextView> (Resource.Id.textView1);
  var textView2 = view.FindViewById<TextView> (Resource.Id.textView2);
  var imageView = view.FindViewById<ImageView> (Resource.Id.imageView);

  textView1.Text = Names [position];
  textView2.Text = Descriptions [position];
  imageView.SetImageResource (Names [position].ToLower().Contains ("xamarin") ?
				   Resource.Drawable.hexagongreen :
				   Resource.Drawable.hexagopurple);

  return view;
}

Now we will only inflate the view when necessary. This immediately leads to increased performance of our ListView, but we can do even better!

Implement a ViewHolder

The second issue I noted was that we are having to locate the controls with FindViewById every single time GetView is called, which can lead to performance issues. The way to get around having to use FindViewById is to implement a “ViewHolder” design pattern.

We will create a ViewHolder class to store all of the controls that are inside of the view so we can access them immediately without the need to use FindViewById each time. The first thing we will do is create a class to store the information:

private class MyViewHolder : Java.Lang.Object 
{
  public TextView Name { get; set; }
  public TextView Description { get; set; }
  public ImageView Image { get; set; }
}

You might be wondering why we are inheriting from Java.Lang.Object, well the reason for this is because each view has a Tag property, that can be used to store information. The Tag property is of type Java.Lang.Object, so that forces us to inherit from it. Now that we have the ViewHolder setup we can finish the optimization by populating it, storing it inside the view, and accessing the controls from the holder instead of calling FindViewById:

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.Name = view.FindViewById<TextView> (Resource.Id.textView1);
    holder.Description = view.FindViewById<TextView> (Resource.Id.textView2);
    holder.Image = view.FindViewById<ImageView> (Resource.Id.imageView);
    view.Tag = holder;
  } 


  holder.Name.Text = Names [position];
  holder.Description.Text = Descriptions [position];
  holder.Image.SetImageResource (Names [position].ToLower().Contains ("xamarin") ?
				 Resource.Drawable.hexagongreen :
				 Resource.Drawable.hexagopurple);

  return view;
}

There you have it, with just a few quick changes you will have your Adapters fully optimized to create the best user experience possible for your app. A few other notes, while I only mentioned the ListView these same optimizations can be used on your adapters for GridViews as well. Additionally, it is recommended to inherit from BaseAdapter instead of using an ArrayAdapter when exposing C# objects to avoid unnecessary bridge interaction when working in Xamarin.Android.

Discuss this blog post in the Xamarin Forums

Author

James Montemagno
Principal Manager, Tech PM

James Montemagno is a Principal Lead Program Manager for Developer Community at Microsoft. He has been a .NET developer since 2005, working in a wide range of industries including game development, printer software, and web services. Prior to becoming a Principal Program Manager, James was a professional mobile developer and has now been crafting apps since 2011 with Xamarin. In his spare time, he is most likely cycling around Seattle or guzzling gallons of coffee at a local coffee shop. He ...

More about author

0 comments

Discussion are closed.