There are times when you want to display data as a list. For example a list of emails, a list of messages, a list of tweets, a list of post titles, a list of photos with their titles and other meta data like dates and summary. Android provides 2 classes with the help of which we can achieve a scrollable list of items in our mobile application:
- ListView – Show items in a vertically scrolling list.
- ExpandableListView – Similar to
ListView
but supports grouping of items that can be expanded individually to show its childrens.
The list has to be populated using any Adapter of the ListAdapter type like ArrayAdapter
and SimpleCursorAdapter
which are the most commonly used ones.
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
Building a Sample List
Let’s quickly build a sample list using ArrayAdapter whose data source is an array of Java objects. Firstly, we’ll create a layout file for our ListView
at /layout/activity_contacts.xml
with the following code:
<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/listview" android:layout_width="wrap_content" android:layout_height="wrap_content" />
Note: You can also embody the ListView
in a LinearLayout
. But I’ll keep it simple for now.
Then in the Activity’s onCreate
method put this:
// List of items String[] items = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10", "Item 11", "Item 12", "Item 13", "Item 14", "Item 15" }; // Create an array adapter ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, items); // Create a ListView object ListView listView = (ListView) findViewById(R.id.listview); // set the adapter to the view listView.setAdapter(adapter);
That’s it! We built our first scrollable list on Android using ListView
fed with data by ArrayAdapter
. If you run this code on your mobile or emulator then you’ll see a list with 15 items.
The 3 arguments that we pass to the ArrayAdapter constructor are:
- The app Context.
- The layout that contains a
TextView
for instantiating views for each item. In this case we usedandroid.R.layout.simple_list_item_1
which is a built-in XML layout in Android. - The list of objects as an array.
To customize the appearance we can:
- Create a customized layout file ourselves, representing each item. For example one with an ImageView and multiple TextViews.
- Extend the ArrayAdapter class and override the
getView()
method to modify the views for each items and return.
Using SimpleCursorAdapter
This adapter is supposed to be used when the data comes from a Cursor (provides random read-write access to result sets returned by a database query).
Let’s create a simple list view that’ll fetch all the contacts from your phonebook and show up in a list using SimpleCursorAdapter
. We’ll create a layout file representing each item in the list at /layouts/contact_item.xml
:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="New Text" android:id="@+id/contact_name" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:textSize="20dip" android:textStyle="bold" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="New Text" android:id="@+id/phone_number" android:layout_below="@+id/contact_name" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" /> </RelativeLayout>
Here’s the java code that goes into the Activity:
// Columns from DB to map into the view file String[] fromColumns = { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER }; // View IDs to map the columns (fetched above) into int[] toViews = { R.id.contact_name, R.id.phone_number }; Cursor cursor = getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null ); SimpleCursorAdapter adapter = new SimpleCursorAdapter( this, // context R.layout.contact_item, // layout file cursor, // DB cursor fromColumns, // data to bind to the UI toViews, // views that'll represent the data from `fromColumns` 0 ); // Create the list view and bind the adapter ListView listView = (ListView) findViewById(R.id.listview); listView.setAdapter(adapter);
This should give you a list of all the contacts fetched from the Contacts Provider, displaying the display name of the contact along with the phone number.
Custom Adapter Implementation
The preceding ArrayAdapter
example shows how it maps the string values of each and every item to a single view of the layout file (simple_list_item_1
). If we want further control over the data assignment to layout file and support multiple views, we’ll have to create a custom adapter.
While implementing your own Adapter, you can simply extend the BaseAdapter class, but in this case we’ll extend the ArrayAdapter class itself to get the additional functionalities and benefits of it. Note: ArrayAdapter
is actually a subclass of BaseAdapter
.
Firstly, let’s define a new dummy class called Contacts
where we can store name and numbers:
class Contacts { private String name; private String number; public Contacts(String name, String number) { this.name = name; this.number = number; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setNumber(String number) { this.number = number; } public String getNumber() { return number; } }
Next, here’s the code that’ll go into the onCreate
method that creates a list of contacts using ArrayList
and passes it on to our custom adapter. At the same time the custom adapter object is hooked on to the list view.
ArrayList<Contacts> contacts = new ArrayList<Contacts>(); contacts.add( new Contacts("Name 1", "9999999999") ); contacts.add( new Contacts("Name 2", "8888888888") ); contacts.add( new Contacts("Name 3", "7777777777") ); contacts.add( new Contacts("Name 4", "6666666666") ); contacts.add( new Contacts("Name 5", "5555555555") ); contacts.add( new Contacts("Name 6", "4444444444") ); contacts.add( new Contacts("Name 7", "3333333333") ); contacts.add( new Contacts("Name 8", "2222222222") ); contacts.add( new Contacts("Name 9", "1111111111") ); contacts.add( new Contacts("Name 10", "0000000000") ); CustomAdapter adapter = new CustomAdapter(this, contacts); ListView listView = (ListView) findViewById(R.id.listview); listView.setAdapter(adapter);
Finally, here’s our custom adapter where we override the getView()
method to inflate the /layouts/contact_item.xml
(created in the previous example) file into views and binding data to them according to our needs:
class CustomAdapter extends ArrayAdapter { private Context mContext; private ArrayList<Contacts> mList; public CustomAdapter(Context context, ArrayList<Contacts> list) { super(context, R.layout.contact_item, list); mContext = context; mList = list; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; if (convertView == null) { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.contact_item, null); } else { view = convertView; } TextView contactNameView = (TextView) view.findViewById(R.id.contact_name); TextView phoneNumberView = (TextView) view.findViewById(R.id.phone_number); contactNameView.setText( mList.get(position).getName() ); phoneNumberView.setText( mList.get(position).getNumber() ); return view; } }
The getView()
method is called internally where the adapter inflates the layout for each row and assign the data to individual views in the row (layout). The code inside this method is pretty straightforward:
- Get an instance to the inflater if
convertView
is null and inflate the layout file. - Find the Views by IDs and set their data (text in this case).
- Return the view which gets rendered in the UI.
Note: The layout inflator service can be accessed in 2 ways:
- Via the
getLayoutInflator()
method on the activity object. - Via the
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
method call that has to be casted to(LayoutInflater)
object.
When extending the BaseAdapter class to write your own Custom Adapter, if you’re using cursors then you can simply change cursor positions using moveToPosition()
in the getView()
method and then fetch data from it to display into the Views. We can also store the cursor data into an array (using a while loop with cursor.moveToNext()
as the condition) and then use that if you don’t want to work with cursors in your adapter implementation.
ListActivity and ListFragment
If you’ve understood the basics of list views by now, then getting the hang of ListActivity and ListFragment is super easy. These 2 classes are basically a specialized activity and fragment that simplifies building and handling lists. They extend the Activity
and Fragment
classes respectively.
Here’s a couple of advantages with these classes:
- We don’t have to explicitly assign a layout to them. They host a ListView, i.e., have a default layout containing a single list view. It is possible to set up our own ListView object, but it should strictly have the id
@android:id/list
. - When defining your own layout files, you can specify a
TextView
with the ID@android:id/empty
that’ll show up when the list is empty. - We can call the
setListAdapter()
method to bind an adapter to the default ListView. Earlier, we had to get the ListView object usingfindViewById()
and then call thesetAdapter()
method on that. - We can override the
onListItemClick()
method for handling the taps (selections) of list items directly.
If you have to define custom views, layouts and adapters, then it’s the same as how we did in the previous examples.
ExpandableListView
This is a view similar to ListView
but allows us to define a two-level scrollable list. The first level is a bunch of groups that can be expandanded to show the children which is the second level.
In this case we must have 3 layouts:
- First one containing the main
ExpandableListView
view. - Second one for the groups.
- Third one for the children (detail rows).
We’ll build something based on the previous examples only. So here’s the main layout at /layout/activity_contacts.xml
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:background="#f4f4f4" > <ExpandableListView android:id="@+id/listView" android:layout_height="match_parent" android:layout_width="match_parent"/> </LinearLayout>
Here’s the second layout for groups at /layout/contact_group.xml
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="8dp"> <TextView android:id="@+id/textView1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft" android:textSize="17dp" android:text="Test" /> </LinearLayout>
Finally, here’s the layout for the child rows at /layout/contact_item.xml
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="55dip" android:orientation="vertical"> <TextView android:id="@+id/textView1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="17dip" android:paddingTop="5dp" android:paddingBottom="5dp" android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft" android:text="Test" /> </LinearLayout>
Let’s create a class (can be an inner class within the Activity) to represent our groups and hold children.
public class Group { public String mName; public ArrayList<String> children = new ArrayList<String>(); public Group(String name) { mName = name; } }
Next, we’ll need to define a method inside the Activity to prepare the data. We’ll name it prepareData
:
public ArrayList<Group> prepareData() { Group group1 = new Group("John Doe"); group1.children.add("[email protected]"); group1.children.add("[email protected]"); Group group2 = new Group("Julio Boes"); group2.children.add("[email protected]"); group2.children.add("[email protected]"); Group group3 = new Group("Ron Osmun"); group3.children.add("[email protected]"); group3.children.add("[email protected]"); Group group4 = new Group("Angelica Tebbs"); group4.children.add("[email protected]"); group4.children.add("[email protected]"); Group group5 = new Group("Temika Benning"); group5.children.add("[email protected]"); group5.children.add("[email protected]"); ArrayList<Group> groups = new ArrayList<Group>(); groups.add(group1); groups.add(group2); groups.add(group3); groups.add(group4); groups.add(group5); return groups; }
In the onCreate
method of our Activity, here’s how we’ll initialize the ExpandableListView
along with a custom adapter that we will write in a few seconds and bind the latter to the former.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_contacts); // Expandable list view ExpandableListView listView = (ExpandableListView) findViewById(R.id.listView); ArrayList<Group> groups = prepareData(); final CustomExpandableListAdapter adapter = new CustomExpandableListAdapter(this, groups); listView.setAdapter(adapter); }
Finally, let’s get on to our custom adapter now. Our adapter must extend BaseExpandableListAdapter (might remind you of the equivalent BaseAdapter
class for ListViews) which is an indirect subclass of ExpandableListAdapter
. Like the getView()
method we worked with earlier, this time we’ll need to have 2 methods:
getGroupView()
– Returns View for the Group items/header.getChildView()
– Returns View for the Child items/rows.
There are certain methods that we must override from the android.widget.ExpandableListAdapter interface which is implemented by the BaseExpandableListAdapter
class like getGroupCount()
, getChildrenCount()
, getGroup()
, getChild()
, getGroupId()
, getChildId()
, hasStableIds()
, getGroupView()
, getChildView()
and isChildSelectable()
.
Time to write down our own implementation which will be pretty simple and straightforward:
public class CustomExpandableListAdapter extends BaseExpandableListAdapter { private Context mContext; private ArrayList<Group> mGroups; private LayoutInflater mInflater; public CustomExpandableListAdapter(Context context, ArrayList<Group> groups) { mContext = context; mGroups = groups; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public int getGroupCount() { return mGroups.size(); } @Override public int getChildrenCount(int groupPosition) { return mGroups.get(groupPosition).children.size(); } @Override public Object getGroup(int groupPosition) { return mGroups.get(groupPosition); } @Override public Object getChild(int groupPosition, int childPosition) { return mGroups.get(groupPosition).children.get(childPosition); } @Override public long getGroupId(int groupPosition) { return 0; } @Override public long getChildId(int groupPosition, int childPosition) { return 0; } @Override public boolean hasStableIds() { return false; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.contact_group, null); } // Get the group item Group group = (Group) getGroup(groupPosition); // Set group name TextView textView = (TextView) convertView.findViewById(R.id.textView1); textView.setText(group.mName); return convertView; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.contact_item, null); } // Get child name String children = (String) getChild(groupPosition, childPosition); // Set child name TextView text = (TextView) convertView.findViewById(R.id.textView1); text.setText(children); /*convertView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(mContext, children, Toast.LENGTH_SHORT).show(); } });*/ return convertView; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } }
The implementation is pretty simple and self-explanatory. You might want to setup click listeners on groups and children to show up a Toast containing the names. Here’s how you can do that in the onCreate
method.
listView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { Group group = (Group) adapter.getGroup(groupPosition); Toast .makeText(getApplicationContext(), group.mName, Toast.LENGTH_SHORT) .show(); return false; } }); listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { String child = (String) adapter.getChild(groupPosition, childPosition); Toast .makeText(getApplicationContext(), child, Toast.LENGTH_SHORT) .show(); return false; } });
We bound click listeners to groups and children respectively. There are 2 interesting things to note here:
- We used
getApplicationContext()
to fetch the context to pass to themakeText()
method. - We used
getGroup()
andgetChild()
methods on the adapter directly to get relevant data.
Conclusion
I hope this article gives you a good understanding of how to implement lists in android using various methods and in-built APIs. Feel free to intiate further discussion in the comments below.
Thanks for this nice article.Very Helpful in learning.
Getting an error
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.Practice.activity_life/com.Practice.activity_life.MainActivity}: java.lang.NullPointerException
Using ExpandableListView in Navigation Drawer.
Any help appreciated.
Problem is solved
hii Rishabh i want to use expandable navigation drawer with tab bar .so plz give me suggestion how to implement .or source code link.
thank you
Hi sunil,
I’m yet to write something on expandable navigation drawer but have you checked the android documentation and design guide links:
– https://developer.android.com/design/patterns/navigation-drawer.html
– http://developer.android.com/training/implementing-navigation/nav-drawer.html
For tab bars check out this article – http://codetheory.in/android-swipe-views-with-tabs/
Hope that helps!
Sir please can you post the screenshot of how the output will appear of this code
Thanks in adv
Hi aayu,
I’ll post the screenshots in a few days. Why don’t you test the code in Eclipse or Android Studio till then and run it in the emulator? Should be fairly quick 🙂
Cheers!
OK Sir, I will do that.
Meanwhile can you tell me a solution to problem??I am running Expandable List View which I made using base adapter class BUT the problem is Groups are displaying in random order, What could be the solution??
Sir can I get your response as email??
Whats your view on following query ?
http://stackoverflow.com/questions/27647729/android-listview-multiple-sticky-sections-refer-to-images
Hi Manish,
It seems like you basically need multiple ListViews. But one problem with that can be when you have 3 long ListViews that exceed the real estate of the screen, they might end up having scrollbars or the entire thing may not scroll at all because ListViews are consumers of space.
What you could try is set the height for each ListView dynamically.
Hope that helps!
Hi sir,
I am amit , want to create simple listview by using set & put data with two or more columns ,but did not get it .if you have some free time then please reply me i knew you could be solved it .
I am android beginer hope will get response earliear from u regarding that.