Listview with AlphabetIndexer and cursoradapter on Android

posted on February 10 2013 - 23:09
android java androiddev

What we are going to implement is an ListView with a fast scroller using AlphabetIndexer where the user can scroll it using the fast scroller thumb! We will use a CursorAdapter class where the cursor with the data from the database is passed.

So first of all create your layout like this, its simple:

<?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">
    <ListView
        android:id="@+id/listView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
    </ListView>
</LinearLayout>

The first class that will be create is the class that deals with database, and it will be named DBHandler.java. This class creates an inner class that extends SQLiteOpenHelper. But it doesn’t matter in this case, our main focus is create a ListView with an AlphabetIndexer using CursorAdapter. So create this class, name it whatever you want, I called it DBHandler.java.

import java.util.Random;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import android.widget.Toast;

public class DBHandler
{

private static final String sDB_NAME = "database";
private static final String sTABLE_NAME = "person";
private static final int sDATABASE_VERSION = 1;

private static final String sCREATE_DATABASE = " CREATE TABLE IF NOT EXISTS "
        + sTABLE_NAME
        + " ( _id integer primary key autoincrement, person varchar(50) not null);";

private Context mContext;
private DatabaseHelper mDatabaseHelper;
private SQLiteDatabase mSQLiteDatabase;
private String LOG_TAG = "DBHandler";

public DBHandler(Context context)
{
    this.mContext = context;
    this.mDatabaseHelper = new DatabaseHelper(this.mContext);
}

public static class DatabaseHelper extends SQLiteOpenHelper
{
    Context mContext;

    DatabaseHelper(Context context)
    {
        super(context, sDB_NAME, null, sDATABASE_VERSION);
        this.mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db)
    {
        db.execSQL(sCREATE_DATABASE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
    {
         Toast.makeText(mContext, "This will update the database.", Toast.LENGTH_LONG).show();

            db.execSQL("DROP TABLE IF EXISTS "+sTABLE_NAME);

            onCreate(db);

    }
}

public void open()
{
    mSQLiteDatabase = mDatabaseHelper.getWritableDatabase();
}

public void close()
{
    mDatabaseHelper.close();
}

public void insertPerson()
{
    mSQLiteDatabase.execSQL("DELETE FROM " + sTABLE_NAME);
    ContentValues values = new ContentValues();

    String person = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    Random random = new Random();

    for(int i = 0; i < 100; i++)
    {
        values.put("Person", person.substring(random.nextInt(person.length())));
        mSQLiteDatabase.insert(sTABLE_NAME, null, values);
        Log.i(LOG_TAG , "Inserted " + person.substring(random.nextInt(person.length())));
    }
}

public Cursor selectData()
{
    return mSQLiteDatabase.query(sTABLE_NAME, null, null, null, null, null, "person ASC");
}
}

After that we must create the Activity that will deal with the interface and the CursorAdapter. For that, create an Activity, in this example its called Main.java. In this class an inner class that extends CursorAdapter and implements SectionIndexer is created.

  1. According to CursorAdapter Documentation a CursorAdapter is an adapter that exposes data from a Cursor to a ListView widget. The Cursor must include a column named “_id” or this class will not work.
  2. And according to SectionIndexer Documentation a SectionIndexer is an interface that should be implemented on Adapters to enable fast scrolling in an AbsListView between sections of the list. A section is a group of list items to jump to that have something in common. For example, they may begin with the same letter or they may be songs from the same artist.

Ok, after that explanation, let’s create the Activity, named Main.java.

import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.SectionIndexer;
import android.widget.AlphabetIndexer;
import android.widget.TextView;

public class Main extends Activity
{

private ListView mListView;
private Cursor mCursor;

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    DBHandler dbHandler = new DBHandler(getApplicationContext());
    dbHandler.open();
    dbHandler.insertPerson();

    mCursor = dbHandler.selectData();

    mListView = (ListView) findViewById(R.id.listView);
    mListView.setFastScrollEnabled(true);
    mListView.setAdapter(new MyCursorAdapter(getApplicationContext(),
            android.R.layout.simple_list_item_1,
            mCursor,
            new String[]{"_id, person"},
            new int[]{android.R.id.text1}));
    dbHandler.close();

}

/**
 * Adapter that exposes data from a Cursor to a ListView widget. The Cursor must include a column named "_id"
 * or this class will not work.
 */
public class MyCursorAdapter extends CursorAdapter  implements SectionIndexer
{

    AlphabetIndexer mAlphabetIndexer;

    public MyCursorAdapter(Context context, int simpleListItem1,
            Cursor cursor, String[] strings, int[] is)
    {
        super(context, cursor);

        mAlphabetIndexer = new AlphabetIndexer(cursor,
                cursor.getColumnIndex("person"),
                " ABCDEFGHIJKLMNOPQRTSUVWXYZ");
        mAlphabetIndexer.setCursor(cursor);//Sets a new cursor as the data set and resets the cache of indices.

    }

    /**
     * Performs a binary search or cache lookup to find the first row that matches a given section's starting letter.
     */
    @Override
    public int getPositionForSection(int sectionIndex)
    {
        return mAlphabetIndexer.getPositionForSection(sectionIndex);
    }

    /**
     * Returns the section index for a given position in the list by querying the item and comparing it with all items
     * in the section array.
     */
    @Override
    public int getSectionForPosition(int position)
    {
        return mAlphabetIndexer.getSectionForPosition(position);
    }

    /**
     * Returns the section array constructed from the alphabet provided in the constructor.
     */
    @Override
    public Object[] getSections()
    {
        return mAlphabetIndexer.getSections();
    }

    /**
     * Bind an existing view to the data pointed to by cursor
     */
    @Override
    public void bindView(View view, Context context, Cursor cursor)
    {
        TextView txtView = (TextView)view.findViewById(android.R.id.text1);
        txtView.setText(cursor.getString(
                cursor.getColumnIndex("person")));
    }

    /**
     * Makes a new view to hold the data pointed to by cursor.
     */
    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent)
    {
        LayoutInflater inflater = LayoutInflater.from(context);
        View newView = inflater.inflate(
                android.R.layout.simple_list_item_1, parent, false);
        return newView;
    }
  }
}

Attention to the line 32, if you don’t call (or set it false) mListView.setFastScrollEnabled(true); the fast scroll wont happen. Also, on line 33, it calls the CursorAdapter constructor, passing the layout that it will set the adapter, and also the cursor with the data you want to set and from which columns you want to get the data for which field in the layout you want to print the data.

Now, in the CursorAdapter we implement a SectionIndexer class. And in the CursorAdapter constructor, an AlphabetIndexer variable is declared and we instantiate it passing the cursor with data, the column of the cursor you want to get the data and the letters of the indexes for the AlphabetIndexer. (This letters must always be in Upper Case).

According to AlphabetIndexer Documentation, an AlphabetIndexer is:

  1. A helper class for adapters that implement the SectionIndexer interface. If the items in the adapter are sorted by simple alphabet-based sorting, then this class provides a way to do fast indexing of large lists using binary search. It caches the indices that have been determined through the binary search and also invalidates the cache if changes occur in the cursor.Your adapter is responsible for updating the cursor by calling setCursor(Cursor) if the cursor changes. getPositionForSection(int) method does the binary search for the starting index of a given section (alphabet). In this example, the method setCursor(Cursor) is called on line 61.

The rest of the code is self-explanatory with commentaries, just a few observations, the methods getPositionForSection(Int), and getSection() are mandatorily implemented by SectionIndexer’s class, as well bindView() and newView() by CursorAdapter’s class.

PS: When dealing with view recycling, the newView() method automatically recycles the views.

Well, the result of this tutorial is shown in the image below:

Listview with AlphabetIndexer