Tags

, , , , ,

Android suggests using a ViewHolder pattern to optimize ListViews. There are many blogs about how to use it. Let’s take a simple example.

// Named as row.xml. This is just a skeleton.
<RelativeLayout>

    <ImageView android:id="@+id/icon"/>

    <TextView android:id="@+id/title"
              android:layout_toLeftOf="@id/icon"/>

    <TextView android:id="@+id/subtitle"
              android:layout_toLeftOf="@id/icon"
              android:layout_below="@id/title"/>

</RelativeLayout>

And the code with ViewHolder and getView() will look like this.

class ViewHolder {
    ImageView icon;
    TextView title;
    TextView subtitle;
}

// Inside the adapter
public void getView(int position, View convertView, ViewGroup parent) {
    // if convertView is null, the view is newly inflated.
    // else, re-assign new values.
    ViewHolder holder;
    if (convertView == null) {
        convertView = LayoutInflater.from(context).inflate(R.layout.row, null);

        // Set up the ViewHolder.
        holder = new ViewHolder();
        holder.icon = (ImageView) findViewById(R.id.icon);
        holder.title = (TextView) findViewById(R.id.title);
        holder.subtitle = (TextView) findViewById(R.id.subtitle);

        // Store the holder with the view.
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    // Assign values
    holder.icon.setImageDrawable(some_image);
    holder.title.setText(some_text);
    holder.subtitle.setText(some_text);
}

The reason for ViewHolder pattern is:

  1. The ListViews are optimized (as mentioned here).
  2. The number of calls to findViewById() reduces.

But wait! Did we just add code that resembles a veryLongStatementThatIsHardToInterpret? Also, isn’t ViewHolder a class? Then why can’t we make the complex row a CustomView?

// Custom class for ListRow
public class ListRow extends RelativeLayout {
    private ImageView mIcon;
    private TextView mTitle;
    private TextView mSubtitle;

    public ListRow(Context context, AttributeSet attrs) {
        // RelativeLayout intializations happen here.
        LayoutInflater.from(context).inflate(R.layout.row, this);

        // Store the views.
        mIcon = (ImageView) findViewById(R.id.icon);
        mTitle = (TextView) findViewById(R.id.title);
        mSubtitle = (TextView) findViewById(R.id.subtitle);
    }

    public void setIcon(Drawable drawable) {
        mIcon.setImageDrawable(drawable);
    }

    public void setTitle(String text) {
        mTitle.setText(text);
    }

    public void setSubtitle(String subtitle) {
        mSubtitle.setText(text);
    }
}

// Inside the adapter
public void getView(int position, View convertView, ViewGroup parent) {
    // if convertView is null, the view is newly inflated.
    // else, re-assign new values.
    ListRow row;
    if (convertView == null) {
        // Inflation happens automagically.
        row = new ListRow(context, null);
        convertView = row;
    } else {
        row = (ListRow) convertView;
    }

    // Assign values
    row.setIcon(some_image);
    row.setTitle(some_text);
    row.setSubtitle(some_text);
}

Isn’t the so clear? And the code for the row is abstracted. So, the title can be wrapped inside a LinearLayout. The ImageView can be made to a CustomImageView with borders. But the adapter code remains simple with calls like setIcon() and setTitle().

Note: The XML file’s parent will now be <merge>.