ListView component

ListView displays a vertically-scrollable collection of views, where each view is positioned immediatelybelow the previous view in the list.

At nowadays ListView is used as part of other ui elements to show dropdown as in AutoCompleteTextView. And for displaying the list, a more flexible and efficient RecyclerView is used.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
<ListView
    android:id="@+id/listView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"></ListView>
</FrameLayout>

You must specify adapter to provide data for ListView. Adapter also responsible for views of items in ListView.

listView = findViewById(R.id.listView)

val items = mutableListOf(
    "item0", "item1", "item2", "item3", "item4",
    "item5", "item6", "item7", "item8", "item9"
    )
        
val adapter = MyAdapter(this, items)

You can handle a user click on an item or a long click on an item.

listView.setOnItemClickListener { adapterView: AdapterView<*>, itemView: View, 
                         itemPosition: Int, itemId: Long ->
    Toast.makeText(
        this@MyActivity,
        // adapterView.getItemAtPosition(itemPosition).toString()
        adapter.getItem(itemPosition), 
        Toast.LENGTH_SHORT
        )
        .show()
}

adapters

You can make custom adapter by extending the BaseAdapter class.

If the dataset has changed, the adapter must call the notifyDataSetChanged() method, as the addItem() method in the example does.

To reuse views in a long list check convertView for null in getView() method.

Custom adapter

Android provides some other adapter classes.

class description
ArrayAdapter<T> By default, the array adapter creates a view by calling Object#toString() on each data object in the collection you provide, and places the result in a TextView.
CursorAdapter Abstract class to expose data from a Cursor to a ListView widget.
SimpleAdapter An easy adapter to map static data to views defined in an XML file. You can specify the data backing the list as an ArrayList of Maps. Each entry in the ArrayList corresponds to one row in the list. The Maps contain the data for each row.
SimpleCursorAdapter An easy adapter to map columns from a cursor to TextViews or ImageViews defined in an XML file. You can specify which columns you want, which views you want to display the columns, and the XML file that defines the appearance of these views.
HeaderViewListAdapter Used when a ListView has headers and footers

headers/footers

HeaderViewListAdapter allows you to specify headers and footers for your ListView.

In the custom adapter you must return count of items with the header and footer items and return proper view for them.

"load more" footer example

filtering

In some cases, you need to add a Filterable interface to your adapter, for example when you are working with AutoCompleteTextView.

Adapter with filtering

The performFiltering() method will be performed in background thread by worker, so it cannot touch ui.

Suppose you work with the Realm db, which uses separate instance of realm for each thread. Creation a new realm instance is very costly. In this case, you can perform filtering in publishResults() method.

Built-in adapters like SimpleAdapter already implement Filterable.

view holder

This is deprecated, use RecyclerView instead ListView.

Item view can be complex and contain multiple views like TextView and ImageView. Calling the findViewById() method is costly. To optimize performance you can use a holder class to keep a view references.

class ViewHolder{
    lateinit var txt : TextView
    lateinit var title : TextView
    lateinit var author : TextView
    // ...
}

Rewrite getView() method of adapter to use ViewHolder

override fun getView(position: Int, 
                 convertView: View?, parent: ViewGroup?): View {
    val item = getItem(position)
    val v = convertView ?: mInflater.inflate(idResLayout, parent, false)

    var vholder : ViewHolder = v.tag?.run {this as ViewHolder } 
                       ?: ViewHolder().apply {
                           txt = v.findViewById(itemTextId)
                           title = v.findViewById(itemTitleId)
                           // ...
                           v.tag = this
                          }

     bindView(vholder, item)
     return v
}

fun bindView(vHolder: ViewHolder, item: MyItemObject){
    vHolder.txt.text = item.text
    vHolder.title.text = item.title
    //...
}