|
Earlier today the
latest Android 0.9 SDK was released, and it’s packed full of wonderful changes.
As you play around, you might see ListViews split into sections using separating
headers. (Example shown on the right is the browser settings list.)
There
isn’t an easy way of creating these separated lists, so I’ve put together SeparatedListAdapter
which does it quickly. To summarize, we’re creating a new BaseAdapter that can contain
several other Adapters, each with their own section headers.
First let’s create some simple XML layouts to be used for our
lists: first the header view, then two item views that we’ll use later for the individual
lists. (Thanks to Romain Guy for helping me find existing styles to keep these XML
layouts nice and tidy.)
-
- <TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/list_header_title"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:paddingTop="2dip"
- android:paddingBottom="2dip"
- android:paddingLeft="5dip"
- style="?android:attr/listSeparatorTextViewStyle" />
-
-
- <TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/list_item_title"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:paddingTop="10dip"
- android:paddingBottom="10dip"
- android:paddingLeft="15dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- />
-
-
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingTop="10dip"
- android:paddingBottom="10dip"
- android:paddingLeft="15dip"
- >
- <TextView
- android:id="@+id/list_complex_title"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- />
- <TextView
- android:id="@+id/list_complex_caption"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
- LinearLayout>
Now let’s create the actual SeparatedListAdapter class which
provides a single interface to multiple sections of other Adapters. After using
addSection() to construct the child sections, you can easily use
ListView.setAdapter() to present the now-separated list to users.
As for the Adapter internals, to correctly find the selected
item among the child Adapters, we walk through subtracting from the original
position until we find either a header (position = 0) or item in the current
child Adapter (position < size).
Here’s the source for SeparatedListAdapter:
- public class SeparatedListAdapter extends BaseAdapter {
-
- public final Map sections = new LinkedHashMap();
- public final ArrayAdapter headers;
- public final static int TYPE_SECTION_HEADER = 0;
-
- public SeparatedListAdapter(Context context) {
- headers = new ArrayAdapter(context, R.layout.list_header);
- }
-
- public void addSection(String section, Adapter adapter) {
- this.headers.add(section);
- this.sections.put(section, adapter);
- }
-
- public Object getItem(int position) {
- for(Object section : this.sections.keySet()) {
- Adapter adapter = sections.get(section);
- int size = adapter.getCount() + 1;
-
-
- if(position == 0) return section;
- if(position < size) return adapter.getItem(position - 1);
-
-
- position -= size;
- }
- return null;
- }
-
- public int getCount() {
-
- int total = 0;
- for(Adapter adapter : this.sections.values())
- total += adapter.getCount() + 1;
- return total;
- }
-
- public int getViewTypeCount() {
-
- int total = 1;
- for(Adapter adapter : this.sections.values())
- total += adapter.getViewTypeCount();
- return total;
- }
-
- public int getItemViewType(int position) {
- int type = 1;
- for(Object section : this.sections.keySet()) {
- Adapter adapter = sections.get(section);
- int size = adapter.getCount() + 1;
-
-
- if(position == 0) return TYPE_SECTION_HEADER;
- if(position < size) return type + adapter.getItemViewType(position - 1);
-
-
- position -= size;
- type += adapter.getViewTypeCount();
- }
- return -1;
- }
-
- public boolean areAllItemsSelectable() {
- return false;
- }
-
- public boolean isEnabled(int position) {
- return (getItemViewType(position) != TYPE_SECTION_HEADER);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- int sectionnum = 0;
- for(Object section : this.sections.keySet()) {
- Adapter adapter = sections.get(section);
- int size = adapter.getCount() + 1;
-
-
- if(position == 0) return headers.getView(sectionnum, convertView, parent);
- if(position < size) return adapter.getView(position - 1, convertView, parent);
-
-
- position -= size;
- sectionnum++;
- }
- return null;
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- }
public class SeparatedListAdapter extends BaseAdapter {
public final Map sections = new LinkedHashMap();
public final ArrayAdapter headers;
public final static int TYPE_SECTION_HEADER = 0;
public SeparatedListAdapter(Context context) {
headers = new ArrayAdapter(context, R.layout.list_header);
}
public void addSection(String section, Adapter adapter) {
this.headers.add(section);
this.sections.put(section, adapter);
}
public Object getItem(int position) {
for(Object section : this.sections.keySet()) {
Adapter adapter = sections.get(section);
int size = adapter.getCount() + 1;
// check if position inside this section
if(position == 0) return section;
if(position < size) return adapter.getItem(position - 1);
// otherwise jump into next section
position -= size;
}
return null;
}
public int getCount() {
// total together all sections, plus one for each section header
int total = 0;
for(Adapter adapter : this.sections.values())
total += adapter.getCount() + 1;
return total;
}
public int getViewTypeCount() {
// assume that headers count as one, then total all sections
int total = 1;
for(Adapter adapter : this.sections.values())
total += adapter.getViewTypeCount();
return total;
}
public int getItemViewType(int position) {
int type = 1;
for(Object section : this.sections.keySet()) {
Adapter adapter = sections.get(section);
int size = adapter.getCount() + 1;
// check if position inside this section
if(position == 0) return TYPE_SECTION_HEADER;
if(position < size) return type + adapter.getItemViewType(position - 1);
// otherwise jump into next section
position -= size;
type += adapter.getViewTypeCount();
}
return -1;
}
public boolean areAllItemsSelectable() {
return false;
}
public boolean isEnabled(int position) {
return (getItemViewType(position) != TYPE_SECTION_HEADER);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
int sectionnum = 0;
for(Object section : this.sections.keySet()) {
Adapter adapter = sections.get(section);
int size = adapter.getCount() + 1;
// check if position inside this section
if(position == 0) return headers.getView(sectionnum, convertView, parent);
if(position < size) return adapter.getView(position - 1, convertView, parent);
// otherwise jump into next section
position -= size;
sectionnum++;
}
return null;
}
@Override
public long getItemId(int position) {
return position;
}
}
As expected, it correctly prevents the section headers from
being selected, and seamlessly stiches together the various Adapters.
This approach also uses convertView correctly as long as the
child Adapters return getItemViewType() and getViewTypeCount() normally. No
special changes are needed for an Adapter to become a child.
Now let’s use SeparatedListAdapter in some example code. We
use the XML layouts defined earlier to create an ArrayAdapter and an advanced
two-row SimpleAdapter, and then add both as sections to our
SeparatedListAdapter.
- public class ListSample extends Activity {
-
- public final static String ITEM_TITLE = "title";
- public final static String ITEM_CAPTION = "caption";
-
- public Map createItem(String title, String caption) {
- Map item = new HashMap();
- item.put(ITEM_TITLE, title);
- item.put(ITEM_CAPTION, caption);
- return item;
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- List
- security.add(createItem("Remember passwords", "Save usernames and passwords for Web sites"));
- security.add(createItem("Clear passwords", "Save usernames and passwords for Web sites"));
- security.add(createItem("Show security warnings", "Show warning if there is a problem with a site's security"));
-
-
- SeparatedListAdapter adapter = new SeparatedListAdapter(this);
- adapter.addSection("Array test", new ArrayAdapter(this,
- R.layout.list_item, new String[] { "First item", "Item two" }));
- adapter.addSection("Security", new SimpleAdapter(this, security, R.layout.list_complex,
- new String[] { ITEM_TITLE, ITEM_CAPTION }, new int[] { R.id.list_complex_title, R.id.list_complex_caption }));
-
- ListView list = new ListView(this);
- list.setAdapter(adapter);
- this.setContentView(list);
-
- }
-
- }
public class ListSample extends Activity {
public final static String ITEM_TITLE = "title";
public final static String ITEM_CAPTION = "caption";
public Map createItem(String title, String caption) {
Map item = new HashMap();
item.put(ITEM_TITLE, title);
item.put(ITEM_CAPTION, caption);
return item;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
List> security = new LinkedList>();
security.add(createItem("Remember passwords", "Save usernames and passwords for Web sites"));
security.add(createItem("Clear passwords", "Save usernames and passwords for Web sites"));
security.add(createItem("Show security warnings", "Show warning if there is a problem with a site's security"));
// create our list and custom adapter
SeparatedListAdapter adapter = new SeparatedListAdapter(this);
adapter.addSection("Array test", new ArrayAdapter(this,
R.layout.list_item, new String[] { "First item", "Item two" }));
adapter.addSection("Security", new SimpleAdapter(this, security, R.layout.list_complex,
new String[] { ITEM_TITLE, ITEM_CAPTION }, new int[] { R.id.list_complex_title, R.id.list_complex_caption }));
ListView list = new ListView(this);
list.setAdapter(adapter);
this.setContentView(list);
}
}
The resulting interface behaves just like the browser
preferences list, and you could easily create other custom Adapters to insert
into the various sections, such as including icons or checkboxes.
These section headers can really help separate out
otherwise-cluttered activities. I used them several places in my
CompareEverywhere application which
lets you easily compare prices and read reviews for any product with a barcode.
Source:
jsharkey blog
{mos_fb_discuss:18}
|
How to display a JPG...
Is that posible to zoom in and drag p...
Making a custom Andr...
Cool - I will use it carefully i promise
How to display a JPG...
problem in image loading - i have exe...
Introducing Calculon...
Thank You for your contribution - Hi,...
Debug a Native Appli...
Never mind, I somehow overlooked the ...