Custom list item layout






4.85/5 (11 votes)
Describe how to create list of items with custom defined layout resource
Introduction
In this short article i want to present the fast and easy way customize the view of list of items using the separate resource file.
Background
When we work with the specific framework, some in time, we find that the build in features are not enough. And one of such situation is presenting the list of items. What if do not need to display just list of simple text?
The example in this article is the base line code for feature customization and extension. I try to make it as simple as possible, so i could present only the way to use the resource file for the item layout, as well how to fill it with item data.
How to
As a working environment I use the Eclipse environment with ADT plugin.
To keep the example as simple as possible and focus only on the customizing the list item view, all code base on the clean standard Android Project.
Item layout
First we create new layout file by right click on res/layout folder and choosing New > Android XML File. In the dialog select Resource Type: Layout, Root Element: Linear Layout and enter the File item.xml
.
As an example we will display three values of each item on the list:
- position - the index of the element.
- id - customized identifier of the element.
- item - the value of the item.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":position" />
<TextView
android:id="@+id/id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":id" />
<TextView
android:id="@+id/item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":item" />
</LinearLayout>
To preview the layout in the graphical designer I give the initial values to the android:text
tag. This way our item will look more or less like this:
We could create the different resource files for this layout depending on the screen size, device orientation and resolution. We inherit all the beauty of the resources system in the android. Righ know we will keep it simple as possible.
Custom list adapter
When we know how the item will looks like now we need to implement the adapter. The purpose of this class will creating the item view based on the collection.
On the main application code package (example: net.origami.android.examples
) right click and choose New > Class. Give it the Name CustomAdapter
and as the Superclass enter android.widget.BaseAdapter
.
First we will create constructor.
public CustomAdapter(Context context, String... items) {
_inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
_items = items;
}
As the parameters we get the context
with give us access to the application and system resources and our collection of items
. For this example I chose strings but it could be collection of any types that work for you, maybe some custom domain object.
What i will remember in my adapter are:
_inflater
- the system service that read the resource and instantiate theView
from it._items
- my collection to display.
Next we override the simple methods:
@Override
public int getCount() {
return _items.length;
}
@Override
public Object getItem(int position) {
return _items[position];
}
@Override
public long getItemId(int position) {
return position + 1;
}
- number of item in the collection.getCount
getItem
- allow to get the item at the specific position.getItemId
- allow to get the custom identifier of the item. This could be, but not need, a position. In this example we change the 0 based index to start form 1 to n.
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 1.
View view = _inflater.inflate(R.layout.item, null);
// 2.
TextView positionText = (TextView) convertView
.findViewById(R.id.position);
TextView idText = (TextView) convertView.findViewById(R.id.id);
TextView itemText = (TextView) convertView.findViewById(R.id.item);
// 3.
positionText.setText("position: " + position);
idText.setText("id: " + getItemId(position));
itemText.setText("item: " + getItem(position));
return view;
}
We implement the method with performing three general steps:
- Create the item view based on resource layout.
We use the remembered in constructor_inflater
. - Find all customized elements.
All identifiable texts, images. - Customize the item view based on
position
.
Assigning the texts, loading images as well as showing, hiding some elements if needed.
Optimization
The example is not fully workable we do not use of the adapter yet. Anyway the execution optimization in mobile word is the key factor i will make the digression now.
Feature investigation of the getView calls shows that it is executed 3 times for each visible item on the list before it will be displayed for the first time. User maybe will not see much different on the screen but could notice that our app "eat the battery" very fast switching between views.
This happen because of the layout logic in the android. The
measure
, layout
, and measure
are this three passes. This was the reason why the adapter (in real this optimization is implemented in ListView
) has build in optimization the contenerView
.
The containerView
is the parameter that give us possibility to reuse previously inflated item view object to customize it for other item. We do not need care how its happen just know that if the containerView
is not null
we can reuse it. So after optimization our getView
implementation will look like this:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 1.
if (convertView == null) {
convertView = _inflater.inflate(R.layout.item, null);
}
// 2.
TextView positionText = (TextView) convertView
.findViewById(R.id.position);
TextView idText = (TextView) convertView.findViewById(R.id.id);
TextView itemText = (TextView) convertView.findViewById(R.id.item);
// 3.
positionText.setText("position: " + position);
idText.setText("id: " + getItemId(position));
itemText.setText("item: " + getItem(position));
return convertView;
}
As you could see only the first step changed dramatically. The view variable was also removed while we use the parameter.
HOW DOES IT WORK? As I mention the
getView will be called and the view that will disappear will be reused for the item that will just show up. No more inflating of the resources. See the reference material form I/O 2010 session for details. |
There is one more optimization we could make. Searching of the customized elements could be also power consuming. Maybe it is not so spectacular as the converterView parameter but we should do our best to give good quality and optimized software. Look at the code:
static class ViewHolder {
TextView positionText;
TextView idText;
TextView itemText;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
// 1.
convertView = _inflater.inflate(R.layout.item, null);
// 2.
holder = new ViewHolder();
holder.positionText = (TextView) convertView
.findViewById(R.id.position);
holder.idText = (TextView) convertView.findViewById(R.id.id);
holder.itemText = (TextView) convertView.findViewById(R.id.item);
convertView.setTag(holder);
} else {
// 2.
holder = (ViewHolder) convertView.getTag();
}
// 3.
holder.positionText.setText("position: " + position);
holder.idText.setText("id: " + getItemId(position));
holder.itemText.setText("item: " + getItem(position));
return convertView;
}
The new internal class ViewHolder
was created. It is the cache container for result of search elements for customization. We will search them one time for each created item view and store it in the item view itself as the tag. When the convertView
is reused we simple retrieve the ViewHolder
from tags, no need to search the item view for customized elements again.
Use of adapter
Now its the time to display the list of our items.
First we need to add ListView to the main activity view resource (main.xml
):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/description"
/>
<ListView
android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
And assign our adapter with collection to it when the activity is created (MainActivity.java):
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView list = (ListView) findViewById(R.id.list);
list.setAdapter(new CustomAdapter(this,
"Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6",
"Item 7", "Item 8", "Item 9", "Item 10", "Item 11", "Item 12",
"Item 13", "Item 14", "Item 15", "Item 16", "Item 17",
"Item 18", "Item 19", "Item 20"));
}
This is it!
Selection action
One more thing that could be useful is to accessing the selected item on the list. Typical it will show us the details of the item. For this example I will display the simple Toast popup with the same data that are displayed in the item view.
To do so just add the lines bellow just after setting the adapter.
list.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { Toast toast = Toast.makeText( getApplicationContext(), "position: " + position + ", id: " + id + ", value: " + adapter.getItemAtPosition(position), Toast.LENGTH_LONG); toast.show(); } });
Feature
This is of course one way to work with ListView
other possibilities are:
ListActivity
- use this as the base class of your activity and mark theListView
elemnt with id attribute set to@android:id/list
. This is useful when we want to display the list on the whole screen. Is such case we do not need to search theListView
in the code, just bind our adapter to the activity usingsetListAdapter
method.ListFragment
(since API 11) - the same as ListActivity but in theFragment
way.SimpleCursorAdapter
- build in adapter to work with theCursor
object that could be retrived form the Database as well as Content Provider. In the constructor we pass theCursor
as well as the item layout, fields to map and corresponding ids of the item layout elements.
ListView
and the adapter have lots more features like displaying diffident item types with different customized views. Of course with reusing views. Headers, footers, etc. But this is another story... if you curies see the Google I/O session listed in the references section.References
- The world of ListView - Google I/O 2010 session available as video and slides (in pdf).
History
- 2012-01-21 - initial version of the article.