How to Create an Android ListView with an Alphabet Scroller

Default blog image of logo on grey
When presenting data in a ListView it may be helpful to have a scroller down the side to allow users to quickly jump to a specific letter.

At Brightec, we develop high-performance Android applications that deliver great results for our clients. Every app we develop is built with the user in mind and through thoughtful tech development, we endeavour to make peoples’ lives easier.

In this article, we will show you how to create an Android ListView with an alphabet scroller. This popular Android feature offers convenience and ease-of-use to Android users and is a common request from our clients.

What is an Android ListView?

According to Android, “A list view is an adapter view that does not know the details, such as type and contents, of the views it contains. Instead list view requests views on demand from a ListAdapter as needed, such as to display new views as the user scrolls up or down.”

ListView is a feature that is widely used in Android applications. And ListView with an alphabetic scroller is something you will have come across, perhaps without even noticing! An obvious example of ListView in Android is the contact book on your phone. ListViews with an alphabetic scroller are a great way to display data in a scrollable list that allows users to quickly jump to a specific letter.

What follows is a how-to guide for presenting data in a ListView on Android.

Android ListView: How to Present Data Correctly

Before creating our Android ListView, it is important we get the other components in place - starting with layouts!

First, create a layout called list_alphabet and add the following 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" >

 <ListView
 android:id="@android:id/list"
 android:layout_width="0dp"
 android:layout_height="match_parent"
 android:layout_weight="1"
 android:fastScrollEnabled="true" />

 <LinearLayout
 android:id="@+id/sideIndex"
 android:layout_width="40dip"
 android:layout_height="fill_parent"
 android:background="#FFF"
 android:gravity="center_horizontal"
 android:orientation="vertical" >
 </LinearLayout>

</LinearLayout>

This gives us a ListView that fills most of the screen, apart from a 40dp column down the right hand side that we’ll put our alphabet scroller in.

Create Android ListView Layout

Next, create a layout called row_item with the following XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="horizontal"
 android:padding="10dp" >

 <TextView
 android:id="@+id/textView1"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="TextView" />

</LinearLayout>

This will be the layout for each of our ListView rows. Finally, create a layout called row_section with the following XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="@android:color/darker_gray"
 android:orientation="horizontal"
 android:paddingBottom="2dp"
 android:paddingLeft="10dp"
 android:paddingTop="2dp" >

 <TextView
 android:id="@+id/textView1"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="TextView"
 android:textColor="@android:color/white"
 android:textStyle="bold" />

</LinearLayout>

This will be the layout for the section headers in our ListView.

Create an AlphabetListAdapter

Next we need to create an adapter that will provide the data to the ListView. Create a new class called AlphabetListAdapter that extends BaseAdapter. The first thing we’ll do in this class is create some models to define our list data:

public static abstract class Row {}

public static final class Section extends Row {
 public final String text;

 public Section(String text) {
 this.text = text;
 }
}

public static final class Item extends Row {
 public final String text;

 public Item(String text) {
 this.text = text;
 }
}

The first is a base class that all the list data will extend, then there is one for a section row and another for an item row. Next we define a property to hold our rows and a setter method:

private List rows;

public void setRows(List rows) {
 this.rows = rows;
}

When you created your class, you should have found some stub methods that need changing so that they use the property that we’ve just defined. These should be:

@Override
public int getCount() {
 return rows.size();
}

@Override
public Row getItem(int position) {
 return rows.get(position);
}

@Override
public long getItemId(int position) {
 return position;
}

The getView method provides a convertView property. This is basically to reduce memory usage. As a row scrolls off the top of the screen, Android will give that View object back to the adapter for re-use so that you don’t have to create more objects than are currently visible.

For this to work properly, you need to tell Android how many view types you’ve got and which view type each row is. This is so that you are only given a suitable View object, otherwise you might be given a View object that doesn’t have the right subviews.

Add Methods to the Adapter

Since we have 2 view types - item and section - we need to add a couple of methods to the adapter:

@Override
public int getViewTypeCount() {
 return 2;
}

@Override
public int getItemViewType(int position) {
 if (getItem(position) instanceof Section) {
 return 1;
 } else {
 return 0;
 }
}

We can now define our getView method:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
 View view = convertView;
 
 if (getItemViewType(position) == 0) { // Item
 if (view == null) {
 LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 view = (LinearLayout) inflater.inflate(R.layout.row_item, parent, false); 
 }
 
 Item item = (Item) getItem(position);
 TextView textView = (TextView) view.findViewById(R.id.textView1);
 textView.setText(item.text);
 } else { // Section
 if (view == null) {
 LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 view = (LinearLayout) inflater.inflate(R.layout.row_section, parent, false); 
 }
 
 Section section = (Section) getItem(position);
 TextView textView = (TextView) view.findViewById(R.id.textView1);
 textView.setText(section.text);
 }
 
 return view;
}

This inflates the relevant view and populates the TextView. With these in place, we can now create our Activity.

Create a ListActivity

Create a new class that extends ListActivity and define the following properties:

private AlphabetListAdapter adapter = new AlphabetListAdapter();
private GestureDetector mGestureDetector;
private List<Object[]> alphabet = new ArrayList<Object[]>();
private HashMap<String, Integer> sections = new HashMap<String, Integer>();
private int sideIndexHeight;
private static float sideIndexX;
private static float sideIndexY;
private int indexListSize;

We also need a custom GestureDetector:

class SideIndexGestureListener extends GestureDetector.SimpleOnGestureListener {
 @Override
 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
 // we know already coordinates of first touch
 // we know as well a scroll distance
 sideIndexX = sideIndexX - distanceX;
 sideIndexY = sideIndexY - distanceY;

 // when the user scrolls within our side index
 // we can show for every position in it a proper
 // item in the country list
 if (sideIndexX >= 0 && sideIndexY >= 0) {
 displayListItem();
 }

 return super.onScroll(e1, e2, distanceX, distanceY);
 }
}

Before writing the onCreate method we’ll define some other methods: populateCountries() just gives us some test data for the ListView:

private List populateCountries() {
 List countries = new ArrayList();
 countries.add("Afghanistan");
 countries.add("Albania");
 countries.add("Bahrain");
 countries.add("Bangladesh");
 countries.add("Cambodia");
 countries.add("Cameroon");
 countries.add("Denmark");
 countries.add("Djibouti");
 countries.add("East Timor");
 countries.add("Ecuador");
 countries.add("Fiji");
 countries.add("Finland");
 countries.add("Gabon");
 countries.add("Georgia");
 countries.add("Haiti");
 countries.add("Holy See");
 countries.add("Iceland");
 countries.add("India");
 countries.add("Jamaica");
 countries.add("Japan");
 countries.add("Kazakhstan");
 countries.add("Kenya");
 countries.add("Laos");
 countries.add("Latvia");
 countries.add("Macau");
 countries.add("Macedonia");
 countries.add("Namibia");
 countries.add("Nauru");
 countries.add("Oman");
 countries.add("Pakistan");
 countries.add("Palau");
 countries.add("Qatar");
 countries.add("Romania");
 countries.add("Russia");
 countries.add("Saint Kitts and Nevis");
 countries.add("Saint Lucia");
 countries.add("Taiwan");
 countries.add("Tajikistan");
 countries.add("Uganda");
 countries.add("Ukraine");
 countries.add("Vanuatu");
 countries.add("Venezuela");
 countries.add("Yemen");
 countries.add("Zambia");
 countries.add("Zimbabwe");
 countries.add("0");
 countries.add("2");
 countries.add("9");
 return countries;
}

displayListItem() is used by the GestureDetector to scroll to the relevant place in the ListView:

public void displayListItem() {
 LinearLayout sideIndex = (LinearLayout) findViewById(R.id.sideIndex);
 sideIndexHeight = sideIndex.getHeight();
 // compute number of pixels for every side index item
 double pixelPerIndexItem = (double) sideIndexHeight / indexListSize;

 // compute the item index for given event position belongs to
 int itemPosition = (int) (sideIndexY / pixelPerIndexItem);

 // get the item (we can do it since we know item index)
 if (itemPosition < alphabet.size()) {
 Object[] indexItem = alphabet.get(itemPosition);
 int subitemPosition = sections.get(indexItem[0]);

 //ListView listView = (ListView) findViewById(android.R.id.list);
 getListView().setSelection(subitemPosition);
 }
}

updateList() adds TextViews to the alphabet scroller:

public void updateList() {
 LinearLayout sideIndex = (LinearLayout) findViewById(R.id.sideIndex);
 sideIndex.removeAllViews();
 indexListSize = alphabet.size();
 if (indexListSize < 1) {
 return;
 }

 int indexMaxSize = (int) Math.floor(sideIndex.getHeight() / 20);
 int tmpIndexListSize = indexListSize;
 while (tmpIndexListSize > indexMaxSize) {
 tmpIndexListSize = tmpIndexListSize / 2;
 }
 double delta;
 if (tmpIndexListSize > 0) {
 delta = indexListSize / tmpIndexListSize;
 } else {
 delta = 1;
 }

 TextView tmpTV;
 for (double i = 1; i <= indexListSize; i = i + delta) {
 Object[] tmpIndexItem = alphabet.get((int) i - 1);
 String tmpLetter = tmpIndexItem[0].toString();

 tmpTV = new TextView(this);
 tmpTV.setText(tmpLetter);
 tmpTV.setGravity(Gravity.CENTER);
 tmpTV.setTextSize(15);
 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
 tmpTV.setLayoutParams(params);
 sideIndex.addView(tmpTV);
 }

 sideIndexHeight = sideIndex.getHeight();

 sideIndex.setOnTouchListener(new OnTouchListener() {
 @Override
 public boolean onTouch(View v, MotionEvent event) {
 // now you know coordinates of touch
 sideIndexX = event.getX();
 sideIndexY = event.getY();

 // and can display a proper item it country list
 displayListItem();

 return false;
 }
 });
}

Populate the Alphabet List

We can now define the onCreate method. After initialising our country list, we loop through it and populate the alphabet list, grouping numeric entries together. We then call updateList() to create the scroller views:

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.list_alphabet);

 mGestureDetector = new GestureDetector(this, new SideIndexGestureListener());

 List countries = populateCountries();
 Collections.sort(countries);

 List rows = new ArrayList();
 int start = 0;
 int end = 0;
 String previousLetter = null;
 Object[] tmpIndexItem = null;
 Pattern numberPattern = Pattern.compile("[0-9]");

 for (String country : countries) {
 String firstLetter = country.substring(0, 1);

 // Group numbers together in the scroller
 if (numberPattern.matcher(firstLetter).matches()) {
 firstLetter = "#";
 }

 // If we've changed to a new letter, add the previous letter to the alphabet scroller
 if (previousLetter != null && !firstLetter.equals(previousLetter)) {
 end = rows.size() - 1;
 tmpIndexItem = new Object[3];
 tmpIndexItem[0] = previousLetter.toUpperCase(Locale.UK);
 tmpIndexItem[1] = start;
 tmpIndexItem[2] = end;
 alphabet.add(tmpIndexItem);

 start = end + 1;
 }

 // Check if we need to add a header row
 if (!firstLetter.equals(previousLetter)) {
 rows.add(new Section(firstLetter));
 sections.put(firstLetter, start);
 }

 // Add the country to the list
 rows.add(new Item(country));
 previousLetter = firstLetter;
 }

 if (previousLetter != null) {
 // Save the last letter
 tmpIndexItem = new Object[3];
 tmpIndexItem[0] = previousLetter.toUpperCase(Locale.UK);
 tmpIndexItem[1] = start;
 tmpIndexItem[2] = rows.size() - 1;
 alphabet.add(tmpIndexItem);
 }

 adapter.setRows(rows);
 setListAdapter(adapter);

 updateList();
}

Finally, define onTouchEvent() to call our custom GestureDetector:

@Override
public boolean onTouchEvent(MotionEvent event) {
 if (mGestureDetector.onTouchEvent(event)) {
 return true;
 } else {
 return false;
 }
}

Putting this all together should give you a working ListView with an alphabetical scrollbar in Android.

Android ListView Example

Android ListView is a convenient way for mobile users to see lots of information at once and quickly find what they are looking for. We hope this article has been helpful!

If you’d like to see an Android ListView example, download our sample project here.

Discover Our Work

Here at Brightec, we develop high-performance mobile, Android, and iOS applications for our clients. Find out more about our work or take your learning to the next level.


Looking for something else?

Search over 400 blog posts from our team

Want to hear more?

Subscribe to our monthly digest of blogs to stay in the loop and come with us on our journey to make things better!