Data items in various types of applications which are displayed as list in UI are list of categories or products in shopping app, list of search results in apps with search functionality, list of articles in blog apps, list of tourist attractions in tourism related apps, list of movies, list of audio, etc.
Android ListView displays data vertically with each item positioned below the previous data items. ListView is scrollable. You can use ListView without any issues for displaying few data items in UI. But for displaying large volumes of data as list, you need to use RecyclerView to build well performing apps.
ListView uses adapter object to display data. ListView adapters are implementations of ListAdapter interface. ListView adapters take list of data items and item layout to be used for each item as input. Once adapter object is crated, it needs to be passed to ListView object by calling setAdapter() method.
Android provides ArrayAdapter and CursorAdapter. ArrayAdapter can be used to display array of items in ListView. CursorAdapter is used for displaying data from Cursor in ListView.
Custom ListAdapter can be created by extending BaseAdapter or its subclasses and implementing getView and other methods. We will see how to create custom listview adapter in the following sections.
First you need to add ListView to layout of the activity or fragment which is responsible for displaying the list screen.
<ListView
android:id="@+id/products"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
In the activity, obtain the ListView object.
val lv = findViewById<ListView>(R.id.products)
Then get list of data from local or remote data base and pass it to list view adapter instance. For examples list of strings can be shown in ListView using ArrayAdapter. Item layout simple_list_item_1 contains TextView which displays string item.
val prodAdapter = ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, prodsList)
Then pass the adapter object to the ListView by calling setAdapter method.
lv.adapter = prodAdapter
To display complex data items meaning data list containing items with each item having several properties, custom list adapter needs to be created. You can create custom ListAdapter by extending ArrayAdapter or BaseAdapter.
In the getView method, using the item position argument passed to it, you need to get data item for that position, then inflate item layout and populate the views in item layout with the data from the data item.
Following Kotlin code shows how to create custom ListAdapter.
import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import java.util.*
class AttractionsAdapter(items: ArrayList<Attraction>, ctx: Context) :
ArrayAdapter<Attraction>(ctx, R.layout.attraction_item, items) {
//view holder is used to prevent findViewById calls
private class AttractionItemViewHolder {
internal var image: ImageView? = null
internal var title: TextView? = null
internal var description: TextView? = null
internal var hours: TextView? = null
}
override fun getView(i: Int, view: View?, viewGroup: ViewGroup): View {
var view = view
val viewHolder: AttractionItemViewHolder
if (view == null) {
val inflater = LayoutInflater.from(context)
view = inflater.inflate(R.layout.attraction_item, viewGroup, false)
viewHolder = AttractionItemViewHolder()
viewHolder.title = view!!.findViewById<View>(R.id.title) as TextView
viewHolder.description = view.findViewById<View>(R.id.description) as TextView
viewHolder.hours = view.findViewById<View>(R.id.hours) as TextView
//shows how to apply styles to views of item for specific items
if (i == 3)
viewHolder.hours!!.setTextColor(Color.DKGRAY)
viewHolder.image = view.findViewById<View>(R.id.image) as ImageView
} else {
//no need to call findViewById, can use existing ones from saved view holder
viewHolder = view.tag as AttractionItemViewHolder
}
val attraction = getItem(i)
viewHolder.title!!.text = attraction!!.title
viewHolder.description!!.text = attraction.description
viewHolder.hours!!.text = attraction.hours
viewHolder.image!!.setImageResource(attraction.image)
//shows how to handle events of views of items
viewHolder.image!!.setOnClickListener {
Toast.makeText(context, "Clicked image of " + attraction!!.title,
Toast.LENGTH_SHORT).show()
}
view.tag = viewHolder
return view
}
}
The custom adapter shown above uses view holder object to avoid findViewById calls if view object exists for the position passed to getView method.
View holder object contains all the views of an item. View holder object is saved as tag in the parent view of item layout when it is created first time. In the getView method, if the view passed to it is not null, you can get the view holder object from the view and access the views of item.
By styling the views of item layout used for displaying data items, you can make the look and feel of the ListView to match your apps theme. To differentiate each item in the list in terms of style, you can use different color, font, text size, etc for items in different positions and apply them in the listadapter to views of the items.
For example, you can assign a different text color to TextView of an item at a particular position in the list in the getView method of listadapter.
if(i == 3)
viewHolder.hours!!.setTextColor(Color.DKGRAY)
You can configure item divider height by setting the height on ListView object.
lv.dividerHeight = 10
If your app needs to respond when an item in the list is clicked to show details of the item clicked or take some action, you need to handle item click events by adding OnItemClickListener to list view as shown below.
lv.onItemClickListener = AdapterView.OnItemClickListener {
adapterView, view, i, l ->
Toast.makeText(this@PlaceAttractionsActivity,
"you selected item " + (i + 1),
Toast.LENGTH_LONG).show()
}
If you need to track and respond to the events of views in items, you need to handle those events in the list adapter by adding appropriate event handler to the views of items. For example, following code adds OnClickListener to image view.
viewHolder.image!!.setOnClickListener {
Toast.makeText(context, "Clicked image of " + attraction!!.title,
Toast.LENGTH_SHORT).show()
}
The ListView Kotlin Example displays a list of tourist attractions of a selected place.
import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.View
class SelectPlaceActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.select_place)
}
fun showAttractions(view: View) {
val i = Intent()
i.setClass(this, PlaceAttractionsActivity::class.java)
startActivity(i)
}
}
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.AdapterView
import android.widget.ListView
import android.widget.Toast
import java.util.*
class PlaceAttractionsActivity : AppCompatActivity() {
private val attractionsByCity = HashMap<String, ArrayList<Attraction>>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.place_attractions)
loadAttractionsData()
val lv = findViewById<ListView>(R.id.place_attractions)
val seattleAttractions = getAttractionsByPlace("Seattle")
val attractionsAdapter = AttractionsAdapter(seattleAttractions, this)
lv.adapter = attractionsAdapter
lv.dividerHeight = 10
lv.onItemClickListener = AdapterView.OnItemClickListener {
adapterView, view, i, l ->
Toast.makeText(this@PlaceAttractionsActivity,
"you selected attraction " + (i + 1),
Toast.LENGTH_LONG).show()
}
}
fun getAttractionsByPlace(place: String): ArrayList<Attraction> {
val attractions = attractionsByCity[place]
if (attractions != null) return attractions else return ArrayList<Attraction>()
}
fun loadAttractionsData() {
addSeattleAttractions(attractionsByCity)
}
fun addSeattleAttractions(attractionsByCity: MutableMap<String, ArrayList<Attraction>>) {
val attractionsLst = ArrayList<Attraction>()
var attraction = Attraction()
attraction.title = "Seattle Space Needle"
attraction.description = "Travel to the top of the Space Needle's 520 ft. " +
"observation deck for unparalleled views of downtown Seattle, " +
"Mt. Rainier, and the Puget Sound,"
attraction.hours = "10AM - 8PM"
attraction.image = R.drawable.space_needle
attractionsLst.add(attraction)
attraction = Attraction()
attraction.title = "Pike Place Market"
attraction.description = "Pike Place Market is a public market, " +
"opened August 17, 1907, and one of the oldest continuously " +
"operated public farmers' markets in the United States."
attraction.hours = "9AM - 4PM"
attraction.image = R.drawable.pike_place_market
attractionsLst.add(attraction)
attraction = Attraction()
attraction.title = "Kerry Park"
attraction.description = "Kerry Park is a 1.26-acre park on the " +
"south slope of Queen Anne Hill in Seattle, Washington, " +
"located at the corner of Second Avenue West and West Highland Drive"
attraction.hours = "4AM - 11PM"
attraction.image = R.drawable.kerry_park
attractionsLst.add(attraction)
attraction = Attraction()
attraction.title = "Mt. Rainier National Park"
attraction.description = "Mount Rainier is the highest mountain of the Cascade" +
" Range of the Pacific Northwest, and the highest mountain" +
" in the U.S. state of Washington."
attraction.hours = "10AM - 5PM"
attraction.image = R.drawable.mountrainier
attractionsLst.add(attraction)
attraction = Attraction()
attraction.title = "The Museum of Flight"
attraction.description = "The Museum of Flight is a private non-profit air " +
"and space museum in the northwest United States. " +
"It is located just south of Seattle."
attraction.hours = "10AM - 5PM"
attraction.image = R.drawable.musium_of_flights
attractionsLst.add(attraction)
attraction = Attraction()
attraction.title = "Snoqualmie Falls"
attraction.description = "Snoqualmie Falls is one of Washington state's" +
" most popular scenic attractions. More than 1.5 million" +
" visitors come to the Falls every year."
attraction.hours = "4AM - 11PM"
attraction.image = R.drawable.snoqualmie_falls
attractionsLst.add(attraction)
attractionsByCity.put("Seattle", attractionsLst)
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginTop="8dp"
tools:context=".PlaceAttractionsActivity">
<ListView
android:id="@+id/place_attractions"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:src="@drawable/kerry_park"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/description"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@+id/image"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/description"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
app:layout_constraintLeft_toRightOf="@+id/image"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<TextView
android:id="@+id/hours_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hours: "
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@+id/image"
app:layout_constraintRight_toLeftOf="@+id/hours"
app:layout_constraintTop_toBottomOf="@+id/description" />
<TextView
android:id="@+id/hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@+id/image"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/description" />
</android.support.constraint.ConstraintLayout>
class Attraction {
var title: String? = null
var description: String? = null
var hours: String? = null
var image: Int = 0
}