Australian Technology

Making a sortable ListView in Android

Takeaway: Creating a list of items in Android is simple, but what if you want to make those items sortable by dragging them? Now things start to get interesting.

One of the missing widgets from Android’s UI toolkit is the abilty to have a sortable list of items. For an example, look at organising a playlist within the Music app and moving songs around, a customised ListView is needed to achieve that functionality.

To begin, below is the final product in a video (download the movie file if you cannot see embedded video).

We will start with a normal list that shows the contents of the list’s items in a Toast dialog when an item is tapped.

I am making the assumption that you are developing for Android with Eclipse and already have the environment and emulators set up and ready to go.

The code for our base Activity, SortableListViewActivity, is:

package com.techrepublic.sortablelistview;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class SortableListViewActivity extends ListActivity {
    private Object[] sArray  = {"Item 0", "Item 1", "Item 2", 42, false, "Item 5", "Item 6"};

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ArrayAdapter adp = new ArrayAdapter(this, R.layout.listrow, sArray);
        setListAdapter(adp);
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
    	String selection = sArray[position].toString();
        Toast.makeText(this, selection, Toast.LENGTH_SHOW).show();
    }
}

This is a simple list that takes an array of Objects, sArray, and presents them to the ListActivity via the ArrayAdapter. The onListItemClick function is called when one of the list items is tapped, and it shows the Toast.

The UI code for the list resides in /res/layout/ and is as follows for main.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="fill_parent"
    android:orientation="vertical" >

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

</LinearLayout>

The id for the ListView must be “@android:id/list” for our ListActivity to work.

For listrow.xml it is:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/title"
    android:layout_width="fill_parent"
    android:layout_height="80px"
    android:gravity="center_vertical"
    android:paddingLeft="6dip"
    />

Spark up the emulator and the code should show Toasts of list items.

Now that we have a list, let’s make it sortable by dragging.

Thanks to the permissiveness of Android’s licensing, we are able to make use of the same component that the Music app’s playlist uses. Navigate over to the github mirror of the Android source and grab a copy of the TouchInterceptor and drop it into the src folder. Make sure to correct the package name and comment out line 390 that sets a background resource that we don’t have, and then change line 126 to cast to a TextView as this is what our list contains:

TextView item = (TextView) getChildAt(itemnum - getFirstVisiblePosition());

To get R.dimen.normal_height and R.dimen.expanded_height to resolve on lines 84 and 86, we need to create a file called res/values/dimens.xml and make its contents:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="normal_height">80dp</dimen>
    <dimen name="expanded_height">160dp</dimen>
</resources>

Return to the main.xml where we need to replace our ListView with the following TouchInterceptor:

<com.techrepublic.sortablelistview.TouchInterceptor
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:drawSelectorOnTop="false"
        android:fastScrollEnabled="true"
        android:textSize="18sp" />

And add to the SortableListViewActivity:


private TouchInterceptor mList;
private TouchInterceptor.DropListener mDropListener =
		    new TouchInterceptor.DropListener() {
		        public void drop(int from, int to) {
		            System.out.println("Droplisten from:"+from+" to:"+to);

		            //Assuming that item is moved up the list
		            int direction = -1;
		            int loop_start = from;
		            int loop_end = to;

		            //For instance where the item is dragged down the list
		            if(from < to) {
		            	direction = 1;
		            }

		            Object target = sArray[from];

		            for(int i=loop_start;i!=loop_end;i=i+direction){
		            	sArray[i] = sArray[i+direction];
		            }

		            sArray[to] = target;

		            System.out.println("Changed array is:"+Arrays.toString(sArray));
		            ((BaseAdapter) mList.getAdapter()).notifyDataSetChanged();
		        }
		    };

I’ve left the debugging output in this version so you can see what is going on.

At the end of SortableListViewActivity’s onCreate method add:

	mList = (TouchInterceptor) getListView();
        mList.setDropListener(mDropListener);
        registerForContextMenu(mList);

If you run the emulator again, it is now possible to drag the list items if you grab them on their far left-hand side. Functionality-wise, it is complete, but we need to make some aesthetic changes to make it usable.

Let’s move the handle for dragging the items onto the right-hand side. We can do that by changing line 132 in the TouchInterceptor to:

 if (item.getRight() - x 

A handle image is added by adding a background attribute to the TextView in listrow.xml:

android:background="@drawable/grablines"

Then we must create the grablines.xml file in /res/drawable and make its contents:

<?xml version="1.0" encoding="utf-8"?>
<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/handle"
    android:gravity="right" />

This file makes an image called handle stick to the right-hand side — the handle image I used is a 10×40 png file that is filled with a solid orange colour and stored in /res/drawable.

Now our handle is visible to the user so that they know where to grab.

Last little thing to do is to clean up listrow.xml by changing the layout_height to the one specified in our dimens file:

android:layout_height="@dimen/normal_height"

And let’s get a little transparency in there by adding an alpha parameter to the window manager parameters in the startDragging method within the TouchInterceptor:

mWindowParams.alpha = 0.5f;

Now you have a fully functional SortableListView to make your Android application that much more user-friendly and intuitive.

Get IT Tips, news, and reviews delivered directly to your inbox by subscribing to TechRepublic’s free newsletters.

Chris Duckett

About Chris Duckett

Programmer and journalist Chris Duckett is the Editor for TechRepublic Australia.

Chris Duckett

Chris Duckett
Chris started his journalistic adventure in 2006 as the Editor of Builder AU after originally joining the company as a programmer. He left CBS Interactive in 2010 to follow his deep desire to study the snowdrifts and culinary delights of Canada and returned to CBS in 2011 as the Editor of TechRepublic Australia, determined to meld together his programming and journalistic tendencies once and for all.
1
Comments

Join the conversation!

Follow via:
RSS
Email Alert