Tuesday 27 November 2012

Implementing the holder pattern in ArrayAdapter

An ArrayAdapter is really cool. You can take any list of data and have it display in a ListView with next to no code.

Lets make a ListView full of Animals.

package com.bradleycurran.android.multiconfigeditor;

public class Animal {

    private String mName;

    private int mImageResourceID;

    public Animal(String name, int imageID) {
        mName = name;
        mImageResourceID = imageID;
    }

    public String getName() {
        return mName;
    }

    public int getImageResourceID() {
        return mImageResourceID;
    }
}

From here, we make a class that extends ArrayAdapter and overrides the getView method.


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;

        if (row == null) {
            LayoutInflater inflator = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflator.inflate(R.layout.row_animal, parent, false);
        }

        TextView name = (TextView)row.findViewById(R.id.text_name);
        ImageView image = (ImageView)row.findViewById(R.id.image_photo);

        Animal animal = getItem(position);

        name.setText(animal.getName());
        image.setImageResource(animal.getImageResourceID());

        return row;
    }

See how we call findViewById every time we create a row? Calling findViewById is an expensive operation, so caching the references will speed up your row creation by a lot.

Create a private inner class called Holder and add the TextView and ImageView as member variables.


    private class Holder
    {
        private TextView mName;
        private ImageView mImage;
    }

Next, create a constructor that takes a row as a parameter.


        public Holder(View row) {
            mName = (TextView)row.findViewById(R.id.text_name);
            mImage = (ImageView)row.findViewById(R.id.image_photo);
        }

Now add a method to Holder called setup that takes an Animal object as a parameter.


        public void setup(Animal animal) {
            mName.setText(animal.getName());
            mImage.setImageResource(animal.getImageResourceID());
        }

Our Holder class is complete! Remove the old code that finds the children Views and sets them up and replace them with the new methods you've written.

Here's the full code for this example:


package com.bradleycurran.android.multiconfigeditor;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class AnimalAdapter extends ArrayAdapter<Animal> {

    public AnimalAdapter(Context context, List<Animal> list) {
        super(context, 0, list);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;

        if (row == null) {
            LayoutInflater inflator = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflator.inflate(R.layout.row_animal, parent, false);
            row.setTag(new Holder(row));
        }

        Holder holder = (Holder)row.getTag();
        holder.setup(getItem(position));

        return row;
    }

    private class Holder {

        private TextView mName;
        private ImageView mImage;

        public Holder(View row) {
            mName = (TextView)row.findViewById(R.id.text_name);
            mImage = (ImageView)row.findViewById(R.id.image_photo);
        }

        public void setup(Animal animal) {
            mName.setText(animal.getName());
            mImage.setImageResource(animal.getImageResourceID());
        }
    }
}

Now the Adapter code is not only cleaner but it runs faster too.

No comments:

Post a Comment