Smartphones

Making a sortable ListView in Android

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 < 20) {

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 10x40 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.

About

Some would say that it is a long way from software engineering to journalism, others would correctly argue that it is a mere 10 metres according to the floor plan.During his first five years with CBS Interactive, Chris started his journalistic advent...

1 comments
jimbocorn
jimbocorn

the line 132 is : if (x < 64) so i cannot change it for if (item.getRight() - x and i dont get why 64 is some kind of limit