Posts Tagged ‘SimpleCursorAdapter’

Making Custom Rows in Android ListViews

Sunday, April 12th, 2009

Hi all,

I’ve been spending the last little while writing software for Google’s Android mobile phone platform and I’ve struggled with a few things along the way.  I thought it might be a good idea to write about some of those struggles and their eventual solutions.  This particular one held me up for several days and, try as I might, an example or solution was nowhere to be found.

In Android you can bind a sqlite database curser directly to a ListView on the display using a furnished adapter called SimpleCursorAdapter.  While this is great it makes a pretty boring ListView by default.  Each row is exactly the same.  I wanted to change the text colour on some rows depending on a field from the database that I wasn’t actually going to display.  First lets have a look at the layouts:

/res/layout/pod_view.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="wrap_content"
		android:layout_height="wrap_content"
		android:text="@string/title"
		android:paddingTop="4pt" />

	<TextView android:id="@+id/title"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:textSize="@dimen/font_size_for_pod_row"
		android:paddingBottom="6pt" />

	<ListView android:id="@+id/android:list"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"/>

</LinearLayout>

Nothing to exotic here, just a ListView with a title at the top.  Notice that the layout_width is set to “fill_parent” on both the outer container, LinearLayout, and the ListView itself.  Without this users will only be able to click the part of each row to select it.  If the text doesn’t go all the way across the row it creates an unclickable dead spot which is a bit confusing.

Now each row also has a layout like this one

/res/layout/pod_row.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView android:id="@+id/text1" xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="@dimen/font_size_for_pod_row"
    android:paddingTop="@dimen/vertical_padding_for_pod_row"
    android:paddingBottom="@dimen/vertical_padding_for_pod_row"/>

Again mind the layout_width attributte.  I’ve stored the values for padding and text size in another spot to make adjustments easy.  For the sake of completeness…

/res/values/dimen.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="font_size_for_pod_row">20sp</dimen>
    <dimen name="vertical_padding_for_pod_row">6pt</dimen>
    <dimen name="font_size_for_show_row">14sp</dimen>
    <dimen name="vertical_padding_for_show_row">3pt</dimen>
</resources>

And that’s how you might do that.  This is handy if you have values you want to reuse and tweak in different layouts throughout your project.

Phwew, done with layouts.

Lets dive in and have a look at the modifications you can make to SimpleCursorAdapter.  I’m not going to talk about setting up a basic SimpleCursorAdapter because there are tonnes of examples out there.  One of them is in the official android tutorial. Here is the function that draws my screen within my Activity:

private void populateFields() {

    // This will be used by our SimpleCursorAdapter to bind fields in each row to
    // data from our cursor.  Note that we have an extra field in the cursor that
    // determines a display attribute for the field we display.
    class ShowViewBinder implements SimpleCursorAdapter.ViewBinder {

        // this cursor is built by calling a function that returns a few things.
        // right now I'm interested in the first (title) and the third
        //(downloaded status)

        private static final int DATA_COLUMN = 1;
        private static final int STATUS_COLUMN = 3;
        boolean retval = false;

        //@Override
        public boolean setViewValue(View view, Cursor cursor, int columnIndex) {

            if ( columnIndex == DATA_COLUMN) {
                int status = cursor.getInt(STATUS_COLUMN);
                TextView tv = (TextView) view;

                switch ( status ) {
                    case PodDbAdapter.DOWNLOADED:
                        tv.setTextColor(Color.GREEN);
                        tv.setText(cursor.getString(DATA_COLUMN));
                        retval = true;
                        break;
                    case PodDbAdapter.TO_DOWNLOAD:
                        tv.setTextColor(Color.rgb(0xFF, 0x99, 0));
                        tv.setText(cursor.getString(DATA_COLUMN));
                        retval = true;
                        break;
                    case PodDbAdapter.DOWNLOAD_ERROR:
                        tv.setTextColor(Color.RED);
                        tv.setText(cursor.getString(DATA_COLUMN));
                        retval = true;
                        break;
                    default:
                        tv.setTextColor(Color.WHITE);
                        tv.setText(cursor.getString(DATA_COLUMN));
                        retval = true;
                        break;
                }
            }
            return retval;
        }
    }

    Cursor pod = mDbHelper.fetchPod(mRowId);
    startManagingCursor(pod);
    mTitleText.setText(pod.getString(pod.getColumnIndexOrThrow(PodDbAdapter.KEY_TITLE)));

    Cursor showsCursor = mDbHelper.fetchShows(mRowId);
    startManagingCursor(showsCursor);

    // Create an array to specify the fields we want to send into our ViewBinder
    String[] from = new String[]{PodDbAdapter.SHOW_KEY_TITLE, PodDbAdapter.SHOW_KEY_STATUS};

    // and an array of the fields we want to bind those fields to (in this case just text1)
    int[] to = new int[]{R.id.text1};

    // Now create a simple cursor adapter and set it to display
    SimpleCursorAdapter shows = new SimpleCursorAdapter(this, R.layout.show_row_white, showsCursor, from, to);
    shows.setViewBinder(new ShowViewBinder());
    setListAdapter(shows);

}

There we have it.  Neat huh?

What I’ve done here is implimented a subclass of SimpleCursorAdapter that was furnished for just such an occasion:  SimpleCursorAdapter.ViewBinder.  My Cursor returns an extra field that we won’t display.  We’ll use that to determine the text colour of the data that is displayed by pulling that field out of the Cursor and having a look.  There are two Cursors in play in my example.  One of them is being used to populate the TextView in my pod_view.xml layout but the second is where the magic happens.  You can see where my ViewBinder is attached down near the bottom with shows.setViewBinder(new ShowViewBinder());

Lastly here’s the result.

device

And there we have it.. Looks like I have a couple of podcasts to listen to ;-)

-Tyson



© All rights reserved.