Customize Android ListView via ListAdapter

Often I find situation where typical listview layout provided by Android’s Hello Listview tutorial is barely adequate. For instance, it does not mention anything about ListAdapter and most of the time I want to get a list that goes beyond one line text. Icon and several textfields are the norm. In this tutorial I will discuss how to create Listview that can be customized with one icons and two text fields via a ListAdapter that is customized just for that particular view.

This post might be a bit longer than usual but should be worth the 10 minutes copy and paste and testing. I modify this example from live code, removing all the sensitive information. let me know if you need more clarification.

0. Create new android project as usual. You can use Android HelloWorld Tutorial as the pointer.

1. Create a bean for object that will populate the field. In this case we create a bean to hold information about options available to create a ‘test’. Basically it is a class containing variable for two strings. You can put this directly in your SRC folder… say CreateTestOption.java

[java]
public class CreateTestOption{
private String name;
private String description;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
[/java]

2. Create the layout for the activity that display the list. This will be the layout for displaying the activity class. You can put this in res/layout folder

[xml]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/white"
>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/dark_blue"
>
<TextView
android:id="@+id/list_header_title"
android:text="@string/create_test_activity_title"
style="@style/listSeparatorTextViewStyle" />
</LinearLayout>
<ListView
android:id="@android:id/list"
android:layout_height="0dip"
android:layout_width="fill_parent"
android:layout_weight="1"
android:scrollbars="vertical"
android:footerDividersEnabled="true"
style="@style/listViewStyle"/>
</LinearLayout>
[/xml]

3. And if you notice, for easier management of more complex project, I define the style and color in separate files namely styles.xml and colors.xml in RES/VALUES folder where original strings.xml is located

styles.xml

[xml]
<?xml version="1.0" encoding="UTF-8"?>
<!– Styling file –>
<resources>
<style name="listSeparatorTextViewStyle" parent="@android:attr/listSeparatorTextViewStyle">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">fill_parent</item>
<item name="android:textSize">15dip</item>
<item name="android:paddingTop">2dip</item>
<item name="android:paddingBottom">3dip</item>
<item name="android:paddingLeft">5dip</item>
<item name="android:paddingRight">10dip</item>
<item name="android:textAppearance">@android:style/TextAppearance.Small</item>
<item name="android:shadowColor">#111111</item>
<item name="android:shadowRadius">1</item>
<item name="android:shadowDy">1</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@android:color/white</item>
<item name="android:background">@color/dark_blue</item>
</style>
<style name="listViewStyle" parent="@android:attr/listViewStyle">
<item name="android:textColor">@android:color/black</item>
<item name="android:background">@color/white</item>
</style>
<style name="genericListItemFirstTextView">
<item name="android:textSize">15dip</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/black</item>
</style>
<style name="genericListItemSecondTextView">
<item name="android:textSize">13dip</item>
<item name="android:textColor">@color/grey/item>
</style>
<style name="genericListItemShoutTextView">
<item name="android:textSize">13dip</item>
<item name="android:textColor">@color/grey</item>
</style>
</resources>
[/xml]

colors.xml

[xml]
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="white">#fff</color>
<color name="black">#000</color>
<color name="red">#c6360a</color>
<color name="green">#688f2b</color>
<color name="orange">#f48905</color>
<color name="dark_blue">#003366</color>
<color name="grey">#888888</color>
</resources>
[/xml]

strings.xml

[xml]
<?xml version="1.0" encoding="utf-8"?>
<!– all the strings required for the app arranged alphabetically–>
<resources>
<!– A –>
<string name="app_name">dummy test</string>
<!– B –>
<!– C –>
<string name="create_test_activity_title">Choose your next test</string>
<string name="create_test_activity_help_button">Help</string>
<string name="customize_test_type_prompt">Set Exam Type</string>
<string name="customize_test_time_prompt">Set Question Type</string>
</resources>
[/xml]

4. Create the layout for the individual item in the list. Name it generic_list_item.xml and put it inside res/layout folder.

[xml]
<?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:paddingLeft="8dip"
android:paddingRight="8dip"
android:paddingTop="5dip"
android:paddingBottom="8dip"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/left_icon"
android:layout_width="32dip"
android:layout_height="32dip"
android:src="@drawable/any_32_32_icon_in_png"
android:scaleType="fitCenter"
android:gravity="center_horizontal"
android:layout_marginTop="3dip" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="8dip" >
<TextView
android:id="@+id/firstLineTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingBottom="2dip"
android:textAppearance="@style/genericListItemFirstTextView"
android:singleLine="true"
android:ellipsize="marquee"/>
<ProgressBar android:id="@+id/progress_horizontal"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="50"
android:visibility="gone"
/>
<TextView
android:id="@+id/secondLineTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:scrollHorizontally="true"
android:ellipsize="marquee"
android:textAppearance="@style/genericListItemSecondTextView" />
</LinearLayout>
</LinearLayout>
[/xml]

5. create adapter class that will populate listview with custom data

[java]
public class CreateTestAdapter extends ArrayAdapter<CreateTestOption> {
public static final String TAG = CreateTestAdapter.class.getSimpleName();
public static final boolean DEBUG = true;
private ArrayList<CreateTestOption> tests;
private LayoutInflater mInflater;
private int layoutResource;
public CreateTestAdapter(Context context, int textViewResourceId,
ArrayList<ViewCreateTestActivity.CreateTestOption> mOptions) {
super(context, textViewResourceId, mOptions);
if (DEBUG) Log.d(TAG, "create UserTestCategoryAdapter(xxx) ");
this.tests = mOptions;
if (DEBUG) Log.d(TAG, "set up LayoutInflater from context");
this.mInflater = LayoutInflater.from(context);
this.layoutResource = textViewResourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (DEBUG) Log.d(TAG, "start getView()");
final ViewHolder holder;
View v = convertView;
if (v == null) {
if (DEBUG) Log.d(TAG, "mInflater inflate history_list_item");
v = mInflater.inflate(layoutResource, null);
if (DEBUG) Log.d(TAG, "set up viewHolder");
holder = new ViewHolder();
holder.icon = (ImageView) v.findViewById(R.id.left_icon);
holder.firstLine = (TextView) v.findViewById(R.id.firstLineTextView);
holder.timeTextView = (TextView) v.findViewById(R.id.secondLineTextView);
if (DEBUG) Log.d(TAG, "attach viewHolder to convertview");
v.setTag(holder);
}else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) v.getTag();
}
CreateTestOption c = tests.get(position);
if (c != null) {
if (DEBUG) Log.d(TAG, "t is not null");
//loading first line
holder.firstLine.setText(c.getName());
holder.firstLine.setVisibility(View.VISIBLE);
//loading second line
holder.timeTextView.setText(c.getDescription());
holder.timeTextView.setVisibility(View.VISIBLE);
//loading icon
//TODO make mechanism so the icon will appear differently
holder.icon.setImageResource(R.drawable.icon_done_32x32);
}
else {
// This is going to be a shout then.
if (DEBUG) Log.d(TAG, "t is null");
holder.icon.setImageResource(R.drawable.ic_menu_shout);
holder.firstLine.setVisibility(View.GONE);
}
//TODO create stringformatter
if (DEBUG) Log.d(TAG, "return v");
return v;
}
private static class ViewHolder {
ImageView icon;
TextView firstLine;
TextView timeTextView;
}
}
[/java]

6. create activity class (at last). for this exercise, just put it in src folder and name it CreateTestActivity.java

This will bind together the layout for activity class,layout for individual list item and adapter for each listitem

[java]
public class CreateTestActivity extends ListActivity {
public static final String TAG = CreateTestActivity.class
.getSimpleName();
public static final boolean DEBUG = true;
private ArrayList<CreateTestOption> m_options = null;
private CreateTestAdapter m_adapter;
private Runnable createTestActivity;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.create_test_activity);
//ListView list = getListView(); //no need since we use ListActivity instead of activity
ensureUi(); //generate the UI and populate its content
}
private void getCreateTestOptions() {
try {
m_options = new ArrayList<CreateTestOption>();
//we generate the value here, in reality, you should retrieve
//the value from other mechanism such as database or web service
CreateTestOption c1 = new CreateTestOption();
c1.setName("Quick Start");
c1.setDescription("Automatically generate a test in a flash");
CreateTestOption c2 = new CreateTestOption();
c2.setName("Recommended Package");
c2.setDescription("Choose from 3 recommended packages");
CreateTestOption c3 = new CreateTestOption();
c3.setName("Design Your Own");
c3.setDescription("Manually customize your own test");
m_options.add(c1);
m_options.add(c2);
m_options.add(c3);
Thread.sleep(15);
} catch (Exception e) {
if (DEBUG)
Log.d(TAG, "exception exception");
Log.e("BACKGROUND_PROC", e.getMessage());
}
//loading the value together with UI thread
//preventing the ‘freeze’ or some sort
runOnUiThread(returnRes);
if (DEBUG)
Log.d(TAG, "getTestCategories() ends");
}
private Runnable returnRes = new Runnable() {
@Override
public void run() {
if (DEBUG) Log.d(TAG, "returnRes runable run() start");
if (m_options != null &amp;&amp; m_options.size() > 0) {
if (DEBUG) Log.d(TAG, "m_tests got something");
m_adapter.notifyDataSetChanged();
if (DEBUG) Log.d(TAG, "m_adapter.notifydatasetchanged() since m_tests got something");
for (int i = 0; i < m_options.size(); i++)
m_adapter.add(m_options.get(i));
}
m_adapter.notifyDataSetChanged();
if (DEBUG) Log.d(TAG, "m_adapter.notifydatasetchanged() after dismiss m_progressdialog");
}
};
private void ensureUi() {
if (DEBUG) Log.d(TAG, "ensureUi() start");
if (DEBUG) Log.d(TAG, "create m_options");
m_options = new ArrayList<CreateTestOption>();
if (DEBUG) Log.d(TAG, "calling CreateTestAdapter()");
this.m_adapter = new CreateTestAdapter(this, R.layout.generic_list_item, m_options);
if (DEBUG) Log.d(TAG, "listView=getListView()");
ListView listView = getListView();
if (DEBUG) Log.d(TAG, "lisview setAdapter this.m_adapter");
listView.setAdapter(this.m_adapter);
listView.setSmoothScrollbarEnabled(true);
if (DEBUG) Log.d(TAG, "initiate new viewTest = new Runable");
createTestActivity = new Runnable() {
public void run() {
getCreateTestOptions();
}
};
if (DEBUG) Log.d(TAG, "create new thread with viewTestRunable");
Thread thread = new Thread(null, createTestActivity, "MagentoBackground");
if (DEBUG) Log.d(TAG, "thread start()");
thread.start();
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapter, View view, int position, long arg3) {
Object obj = (Object)m_adapter.getItem(position);
if (obj != null) {
//TODO set this intent creation based on the unique parameter of the each CreateTestOption class
Intent intent = new Intent();
if (position == 2) {
intent.setClass(ViewCreateTestActivity.this, placeholderActivity2.class);
}else if (position == 1){
intent.setClass(ViewCreateTestActivity.this, placeholderActivity1.class);
}else if (position == 0 ) {
intent.setClass(ViewCreateTestActivity.this, placeholderActivity0.class);
}
startActivity(intent);
}
}
});
}
}
[/java]

7. Define each placeholderActivityN.class as follows – thanks worked

[java]
package com.example.helloandroid;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class placeholderActivity1 extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setText("This is placeholder class #1");
setContentView(tv);
}
}
[/java]

you can do that for the remaining placeholderActivity0 and placeholderActivity2 class. In essence, this will be the target class that you intend to reach upon clicking the list.

8. do not forget to define activity class in in manifest.xml

[xml]
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.combankmed.android" android:versionName="2010-08-02"
android:versionCode="2010080200">
<application

android:icon="@drawable/icon"
android:label="@string/app_name"
>

<activity android:name=".ViewCompleteTestActivity" android:screenOrientation="portrait"></activity>
<activity android:name=".ViewCreateTestActivity" android:screenOrientation="portrait">
<activity android:name=".placeholderActivity0" android:screenOrientation="portrait">
<activity android:name=".placeholderActivity1" android:screenOrientation="portrait">
<activity android:name=".placeholderActivity2" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
<uses-sdk android:minSdkVersion="4" />
</manifest>
[/xml]

5 thoughts on “Customize Android ListView via ListAdapter

  1. worked

    intent.setClass(ViewCreateTestActivity.this, ViewTestCustomizeActivity.class);

    intent.setClass(ViewCreateTestActivity.this, ViewRecommenedTestActivity.class);

    intent.setClass(ViewCreateTestActivity.this, ViewTestTakeActivity.class);

    Having issues with the 3 lines above… were does ViewRecommenedTestActivity, ViewTestTakeActivity and ViewTestCustomizeActivity classes come from? You don’t mention them in your tut?

  2. Aditya Post author

    thank you for the tip off. basically the tutorial is a cleaned snippets from my real project. so some part might not be working.

    i have updated the tutorial at #6 (updated), #7(new), #8(updated).

    i have not really checked the update but it should work. please let me know if its not working

    :)

  3. Patrick

    Some pics would be nice to get an idea of what this looks like before I try adapting the code.

  4. Seto

    Nice post gan. Lebih mantap lagi kalo ada screenshot + project file nya. Jadi nubi kayak ane bisa tinggal import aja gan.

    Keep sharing ya gan.

  5. bianca

    Could you post the screen shot, if possible? Its always nice to see the result before digging in to code…

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>