Friday, October 25, 2013

How to make smooth scrolling ListViews in Android

High performance listviews can improve the user experience

In mobile development today, the user interface and user experience (UI/UX) are king. It’s important to optimize the performance of your application to provide the best experience. Here’s how to optimize the ListView in Android for smooth scrolling.


Android has lagged behind iOS on the UI/UX front due to the increased flexibility provided by the Android platform. While iOS is more limiting on what’s available to the developer, these limitations force key components to be optimized for the best user experience. It’s up to you as a developer to properly code your application for performance in many more cases under Android.
One of the most utilized views in mobile applications is the List View, a vertical scrolling set of elements. It’s important that you construct the list view and it’s layout elements in the proper way in order to achieve efficient performance and smooth scrolling.

ListView creation

When you build a layout in Android, you construct your view in XML, inflate that layout into a View object, then perform an ID lookup on the view for each element - casting it to the proper widget type and binding it onto a variable.
@Override
 public View getView(int i, View view, ViewGroup viewGroup) {
     view = mInflater.inflate(R.layout.twitter_item,viewGroup,false);
     TextView name = (TextView)view.findViewById(R.id.name);
     TextView screen_name = (TextView)view.findViewById(R.id.screen_name);
     TextView tweet =     (TextView)view.findViewById(R.id.tweet);
     TextView created_at = (TextView)view.findViewById(R.id.created_at);
      TwitterItem item = (TwitterItem)getItem(i);
     name.setText(item.getUser().getName());
     screen_name.setText(item.getUser().getScreen_name());
     tweet.setText(item.getTweet());      created_at.setText(TwitterHelper.toFriendlyTwitterDate(item.getCreated_at()));
    return view;
 }

This function will get executed for every cell in a listview to draw the layout. Android employs a recycling technique to avoid inflating the same layout over and over again (since it’s always the same in this case), however the ID look-ups for each specific element are still called every time.
That means for every cell in your list view, CPU cycles are used to look up these same 4 twitter elements every single time. More complicated views can have dozens of elements. The cost of this look up is the number of cells * the number of elements which can grow quickly.

The View Holder Pattern

With the View Holder pattern, a static reference to each element is created and held onto while the ListView is being drawn. This reduces the cost of the element look-ups to 1 * the number of elements, a massive savings in CPU cycles.
Note: Ok, Ok, it’s not quite 1x since the view isn’t always recycled but you get the picture.
Implementing this pattern is pretty simple. To start, make a static class inside your Adapter class to hold your widget elements:
public class TwitterAdapter extends BaseAdapter {
   static class TwitterViewHolder{
       TextView name;
       TextView screen_name;
       TextView twitter;
       TextView created_at; } ... }

Then, modify your getView function.
First, check and see if the view has been initialized yet. If it hasn’t, initialize both the view and the ViewHolder, perform the element look-ups, and set the Tag on the view to hold your ViewHolder object. If it has, get the existing ViewHolder object from the view Tag (saving the ID look-ups).
Next, use the ViewHolder element references to build out your view.

@Override
 public View getView(int i, View view, ViewGroup viewGroup) {
        TwitterViewHolder viewHolder;
       if(view == null){
          view = mInflater.inflate(R.layout.twitter_item,viewGroup,false);
          viewHolder = new TwitterViewHolder();
        viewHolder.name = (TextView)view.findViewById(R.id.name);
        viewHolder.screen_name = (TextView)view.findViewById(R.id.screen_name);       viewHolder.twitter = (TextView)view.findViewById(R.id.twitter);
    viewHolder.created_at = (TextView)view.findViewById(R.id.created_at);    view.setTag(viewHolder);
  }else{
      viewHolder = (TwitterViewHolder)view.getTag();
 }
     TwitterItem item = (TwitterItem)getItem(i);
  viewHolder.name.setText(item.getUser().getName());    viewHolder.screen_name.setText(item.getUser().getScreen_name()); viewHolder.twitter.setText(item.getTwitter()); viewHolder.created_at.setText(TwitterHelper.toFriendlyTwitterDate(item.getCreated_at()));
  return view;
}

A better experience

This can be considered a best practice for ListView coding. Android apps have a terrible reputation for design because optimizations like this are skipped. The beauty of Android is its openness and flexibility, you can do near anything, but with that power comes a responsibility to take the necessary measures to make your creation efficient. Since the ListView control is so common, this tip can go a long way to help Android UI/UX.

No comments:

Post a Comment