If you've ever used ListView, and i bet you had, then you will have noticed that it is not very handy if you want to display a huge amount of data. I found a few solutions, how to categorize list items like in your phones preferences. This post won't be about how it looks rather more about how to write it. I'll show you 2 solutions and describe one if it more detailed. There is no perfect solution, but you can decide which one you prefer.

Solution #1:

One is to include the Sectionview in every item of your list and set its visibility to "GONE". Then make it visible in each first item. I don't like this one, because then you have a lot of  unused TextViews (or whatever) which consumes memory. If you want to read much more about ListView and sectioning, Cyril wrote a great post about it. And here is my preferred way for sectioning list items.

Solution #2:

I didn't like the first version so i thought about my own way to do it. (Before Cyril's post was written :) ). So what did  I do? I created 2 xml files. One for the section and one for the list item. If you want to have your section item look like that one in the preferences you can get its attributes like that:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <include
        android:id="@+id/list_item_section_text"
        layout="@android:layout/preference_category" />
<LinearLayout>

Of course you can design your section header however you want. The second xml is for all the list items. I copied it from another project of mine, you can use it if you want.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:gravity="center_vertical"
    android:paddingRight="?android:attr/scrollbarSize">
    <ImageView
        android:id="@+id/list_item_entry_drawable"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:src="@android:drawable/ic_menu_preferences"
        android:paddingLeft="9dp"/>
    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dip"
        android:layout_marginRight="6dip"
        android:layout_marginTop="6dip"
        android:layout_marginBottom="6dip"
        android:layout_weight="1">

        <TextView
            android:id="@+id/list_item_entry_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:ellipsize="marquee"
            android:fadingEdge="horizontal" />
        <TextView
            android:id="@+id/list_item_entry_summary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/list_item_entry_title"
            android:layout_alignLeft="@id/list_item_entry_title"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:singleLine="true"
            android:textColor="?android:attr/textColorSecondary" />
    </RelativeLayout>
</LinearLayout>

You can see how these  layouts look like on the screenshot above. Next step is the java code. I created 3 classes and one interface.

  • EntryAdapter (ArrayAdapter)
  • Interface: Item
  • SectionItem (implements Item)
  • EntryItem (implements Item)

The Item interface contains the method: isSection(); This will return true when it's a SectionItem and return false when it's a EntryItem so that we can keep the two apart. Item, EntryItem and SectionItem aren't that hard to understand, because there isn't really much code. There are included in the project zip file at the end of the post. The Adapter should be a explained in more detail. Here's the code:

public class EntryAdapter extends ArrayAdapter {
    private Context context;
    private ArrayList items;
    private LayoutInflater vi;
    public EntryAdapter(Context context,ArrayList items) {
        super(context,0, items);
        this.context = context;
        this.items = items;
        vi = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        final Item i = items.get(position);
        if (i != null) {
            if(i.isSection()){
                SectionItem si = (SectionItem)i;
                v = vi.inflate(R.layout.list_item_section, null);
                v.setOnClickListener(null);
                v.setOnLongClickListener(null);
                v.setLongClickable(false);
                final TextView sectionView =
                    (TextView) v.findViewById(R.id.list_item_section_text);
                sectionView.setText(si.getTitle());
            } else {
                EntryItem ei = (EntryItem)i;
                v = vi.inflate(R.layout.list_item_entry, null);
                final TextView title =
                    (TextView)v.findViewById(R.id.list_item_entry_title);
                final TextView subtitle =
                    (TextView)v.findViewById(R.id.list_item_entry_summary);
                if (title != null) title.setText(ei.title);
                if(subtitle != null) subtitle.setText(ei.subtitle);
            }
        }
        return v;
    }
}

The interesting part is the getView() method. Here you have to keep the two apart and inflate either the section xml or the entry xml. Cast the item, then inflate the right xml to get the view. Setup the TextViews or whatever you use in your items and then return the view. Now how to actually add items in your activity (I use ListActivity for this example):

ArrayList items = new ArrayList();
items.add(new SectionItem("Category 1"));
items.add(new EntryItem("Item 1", "This is item 1.1"));
items.add(new EntryItem("Item 2", "This is item 1.2"));
items.add(new EntryItem("Item 3", "This is item 1.3"));
items.add(new SectionItem("Category 2"));
items.add(new EntryItem("Item 4", "This is item 2.1"));
items.add(new EntryItem("Item 5", "This is item 2.2"));
items.add(new EntryItem("Item 6", "This is item 2.3"));
items.add(new EntryItem("Item 7", "This is item 2.4"));
EntryAdapter adapter = new EntryAdapter(this, items);
setListAdapter(adapter);

Pretty simple huh? :) There's just one thing that you have to take care of. In the onListItemClick() method (and long click as well of course) you have to check if the clicked item isn't a Section item.

if(!items.get(position).isSection()){
    EntryItem item = (EntryItem)items.get(position);
    Toast.makeText(this, "You clicked " + item.title , Toast.LENGTH_SHORT).show();
}

Full source: http://files.bartinger.at/SectionListExample.zip

Thats all. It's definitely not the best solution, but I thinks its kinda handy and easy to understand. I tested it with 2000 items and had no problem running it on the emulator and on my device. If you have any questions feel free to leave a comment and I'll reply as soon as possible. So and have a nice day.