Antew

29Nov/120

Using Multiple Layouts in a ListView

Ever wanted multiple layouts in a ListView, perhaps a list of events with a header containing the date, and events for the day below? In this post I will show you one way to accomplish that. The full code for this project is available on Github. In the next post I'll show how to use the ViewHolder pattern to minimize findViewById(int) calls.

The final result

The keys to making this happen are Adapter.getItemViewType(int) and Adapter.getViewTypeCount()

getItemViewType should return an integer that identifies what type of View that will be created by getView(int, View, ViewGroup) for the specified item. Two views should return the same result if one can be converted to the other in getView

getViewTypeCount should return an integer with the number of types of Views that the Adapter will handle.

Behind the scenes Android uses the View type to determine what type of View should be passed in to the getView method. You can think of it as creating multiple buckets of Views that can be reused and thus avoid the penalty of inflating a new view each time a new list item becomes visible.

As the user scrolls down the ListView the framework will recycle rows that are no longer visible and add them to the appropriate bucket for reuse. When a new Header or Event item comes into view the framework will check to see if one of those views already exists in the bucket of recycled views, if so it will be passed in as the convertView to getView.

First, let's start with the Item interface that each row type will implement. getViewType will return the type of View for this row. getView will handle creating a new row or recycling an old one.

public interface Item {
    public int getViewType();
    public View getView(LayoutInflater inflater, View convertView);
}

Next, let's take a look at an Adapter implementation that takes a list of objects implementing Item. You don't have to use an enum, but I think it helps with code clarity here.

public class MyListAdapter extends ArrayAdapter<Item> {
    private List<Item> items;
    private LayoutInflater inflater;

    public enum RowType {
        // Here we have two items types, you can have as many as you like though
        LIST_ITEM, HEADER_ITEM
    }

    public MyListAdapter(Context context, LayoutInflater inflater, List<Item> items) {
        super(context, 0, items);
        this.items = items;
        this.inflater = inflater;
    }

    @Override
    public int getViewTypeCount() {
        // Get the number of items in the enum
        return RowType.values().length;

    }

    @Override
    public int getItemViewType(int position) {
        // Use getViewType from the Item interface
        return items.get(position).getViewType();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Use getView from the Item interface
        return items.get(position).getView(inflater, convertView);
    }
}

Now we need some Items that we can add to the Adapter. Let's begin with the Header type, which is just a LinearLayout containing a TextView.

public class Header implements Item {
    private final String         name;

    public Header(String name) {
        this.name = name;
    }

    @Override
    public int getViewType() {
        return RowType.HEADER_ITEM.ordinal();
    }

    @Override
    public View getView(LayoutInflater inflater, View convertView) {
        if (convertView == null) {
            // No views to reuse, need to inflate a new view
            convertView = (View) inflater.inflate(R.layout.header, null);
        }

        TextView text = (TextView) convertView.findViewById(R.id.headerText);
        text.setText(name);

        return convertView;
    }

}

Next we'll create the EventItem type, which consists of two TextViews in a LinearLayout and shares much of the same code with the Header item type above.

public class EventItem implements Item {
    private final String         str1;
    private final String         str2;

    public EventItem(String text1, String text2) {
        this.str1 = text1;
        this.str2 = text2;
    }

    @Override
    public int getViewType() {
        return RowType.LIST_ITEM.ordinal();
    }

    @Override
    public View getView(LayoutInflater inflater, View convertView) {
        if (convertView == null) {
            convertView = (View) inflater.inflate(R.layout.list_item, null);
        }

        TextView text1 = (TextView) convertView.findViewById(R.id.list_content1);
        TextView text2 = (TextView) convertView.findViewById(R.id.list_content2);
        text1.setText(str1);
        text2.setText(str2);

        return convertView;
    }

}

All that is really left is to create a ListActivity and add some items to our new adapter.

public class MainActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LayoutInflater inflater = LayoutInflater.from(this);
        
        List<Item> items = new ArrayList<Item>();
        items.add(new Header("Friday - November 30th 2012"));
        items.add(new EventItem("8:30am" , "Start work"));
        items.add(new EventItem("9:15am" , "Call Bob"));
        items.add(new EventItem("11:00am", "Meeting with Joe"));
        items.add(new EventItem("5:00pm" , "Freedom!"));
        
        items.add(new Header("Saturday - December 1st 2012"));
        items.add(new EventItem("8:30am" , "Keep sleeping"));
        items.add(new EventItem("10:00am", "Wake up"));
        items.add(new EventItem("11:00am", "Walk the dog"));
        items.add(new EventItem("6:00pm" , "Dinner at John's"));
        
        items.add(new Header("Sunday - December 2rd 2012"));
        items.add(new EventItem("8:30am" , "Keep sleeping"));
        items.add(new EventItem("10:00am", "Wake up"));
        items.add(new EventItem("11:00am", "Walk the dog"));
        items.add(new EventItem("6:00pm" , "Dinner at John's"));

        items.add(new Header("Monday - December 3rd 2012"));
        items.add(new EventItem("8:30am" , "Start work"));
        items.add(new EventItem("9:15am" , "Call Bob"));
        items.add(new EventItem("11:00am", "Meeting with Joe"));
        items.add(new EventItem("5:00pm" , "Freedom!"));
        
        MyListAdapter adapter = new MyListAdapter(this, inflater, items);
        setListAdapter(adapter);
    }

}

 

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.