ListView is a basic Android component for displaying vertical lists, available since API 1 (Android 1.0). ListView creates a list of items using ArrayAdapter and supports custom cell layouts. Let’s figure out what ListView is, how to configure it, and what pitfalls this old but still used component hides. At IT Sectr we encounter ListView in legacy projects and maintain backward compatibility with Android 4.4. For a comparison with the modern alternative, read the article about RecyclerView.
Key Takeaways
ListView is a ViewGroup from the android.widget package designed for displaying a scrollable list of items. ListView implements the AdapterView pattern: data is passed through an adapter (Adapter), which converts data items into Views displayed on the screen. ListView has existed in Android SDK since the first version (API 1) and does not require additional libraries.
Architecture of ListView is simpler than RecyclerView: LayoutManager is built-in (only vertical scrolling), change animations are absent, dividers are set via the android:divider XML attribute. ListView automatically adds OverScrollView and focus highlighting, which simplifies prototyping. According to Statista (2025), about 15% of Android apps in the Play Store top 100 still contain ListView — mainly legacy projects with minimal support.
ListView was the main list component in Android from 2008 to 2014, when RecyclerView was introduced at Google I/O 2014. Despite being partially deprecated (soft-deprecated), ListView remains available in all versions of Android and is not marked as @Deprecated in SDK API 35. If your project supports Android 4.4 (API 19) and below, ListView is the only built-in choice, as RecyclerView requires AndroidX and Gradle dependencies.
ArrayAdapter is the most popular adapter for ListView. It accepts List<T> and a layout for each item. By default, ArrayAdapter calls toString() on each object and displays text in simple_list_item_1 (one TextView). For custom display, you need to override getView() or pass a custom layout in the constructor.
// ListView with ArrayAdapter (simple text)
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val countries = listOf(
"Russia", "USA", "Germany",
"France", "Japan", "China", "Brazil"
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val adapter = ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
countries
)
binding.listView.adapter = adapter
// Click handling
binding.listView.onItemClickListener =
AdapterView.OnItemClickListener { _, _, position, _ ->
Toast.makeText(this, countries[position], Toast.LENGTH_SHORT).show()
}
}
}
SimpleAdapter is designed for lists from Map data. It accepts List<Map<String, Any>> and a from/to array for binding Map keys to widget IDs in the layout. SimpleAdapter is convenient for quick prototypes when a separate adapter class is not needed. The downside is the lack of type-safe typing and complexity with custom logic.
<!-- ListView layout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/divider"
android:dividerHeight="1px"
android:scrollbars="vertical" />
</LinearLayout>
Custom adapter for ListView overrides the getView() method, which RecyclerView splits into onCreateViewHolder + onBindViewHolder. In ListView, both steps are combined, so the developer must independently implement the ViewHolder pattern using convertView (reusable View) and the setTag() method.
// Data model
data class Product(
name: String,
price: String,
imageResId: Int
)
// Custom adapter with ViewHolder
class ProductAdapter(
context: Context,
private val products: List<Product>
) : ArrayAdapter<Product>(context, 0, products) {
// ViewHolder for caching
private class ViewHolder(view: View) {
val nameText: TextView = view.findViewById(R.id.tvName)
val priceText: TextView = view.findViewById(R.id.tvPrice)
val imageView: ImageView = view.findViewById(R.id.ivProduct)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: LayoutInflater.from(context)
.inflate(R.layout.item_product, parent, false)
.also { it.tag = ViewHolder(it) }
val holder = view.tag as ViewHolder
val product = getItem(position)
holder.nameText.text = product.name
holder.priceText.text = product.price
holder.imageView.setImageResource(product.imageResId)
return view
}
}
Key difference from RecyclerView: in ListView, the getView() method is called every time an item appears on the screen. If you do not use convertView and ViewHolder, each scroll creates a new View object, leading to memory leaks and lag on lists with 50+ items. ViewHolder with view.tag is a mandatory optimization for ListView with custom layouts.
ListView has four architectural limitations that Google fixed in RecyclerView: (1) only vertical orientation — no grids, horizontal lists, or staggered layouts; (2) no granular notifications — any data change causes a full redraw via notifyDataSetChanged(), which creates jank; (3) no built-in animation for adding/removing items; (4) no separation of concerns — LayoutManager is built-in, ItemAnimator is absent.
<!-- item_product.xml — ListView item layout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp">
<ImageView
android:id="@+id/ivProduct"
android:layout_width="56dp"
android:layout_height="56dp"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingStart="12dp">
<TextView
android:id="@+id/tvName"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPrice"
android:textSize="14sp"
android:textColor="@color/price" />
</LinearLayout>
</LinearLayout>
Practical implications: on lists up to 20 items, ListView works as fast as RecyclerView. With 100+ items, the difference becomes noticeable: ListView starts to drop FPS due to the lack of diff calculations and forced ViewHolder. For lists with frequent updates (chat, feed, logs), ListView is not suitable — use RecyclerView with Paging 3. At IT Sectr, we migrate ListView to RecyclerView whenever working on legacy screens with more than 50 items.
ListView provides a set of XML attributes for quick configuration without code: android:divider (color/drawable of divider), android:dividerHeight (thickness), android:scrollbars (scroll bars), android:fastScrollEnabled (fast scrolling via handle), android:choiceMode (selection mode: singleChoice, multipleChoice). The android:entries attribute allows populating the list from a resource array (string-array) without an adapter.
| Attribute | Description | Example Value |
|---|---|---|
| android:divider | Color or drawable of the divider | @color/gray_light |
| android:dividerHeight | Divider thickness in px/dp | 1dp |
| android:entries | String array from resources | @array/countries |
| android:choiceMode | Item selection mode | singleChoice |
| android:fastScrollEnabled | Fast scrolling | true |
| android:overlapAnchor | Anchor for popup lists (API 21+) | true |
Selection mode: choiceMode="singleChoice" enables RadioButton-style where the selected item is highlighted. choiceMode="multipleChoice" — checkboxes for multiple items. Selection data is available via listView.checkedItemPosition or listView.checkedItemIds. Important: when using choiceMode, ListView uses StateListDrawable for the background — set your own selector if the default blue does not match the design.
Choice between ListView and RecyclerView depends on project requirements. RecyclerView is the modern standard, but ListView can be justified for simple screens with minimal maintenance costs for legacy code. Comparison across all key parameters:
| Parameter | ListView | RecyclerView |
|---|---|---|
| Library size | Built into SDK (~60 KB) | AndroidX (~300 KB with dependencies) |
| Minimum version | API 1 | API 14 (via AndroidX) |
| Orientation | Vertical only | Vertical, horizontal, grid, staggered |
| ViewHolder | Manual implementation | Required by architecture |
| Animation | Only fade transitions | DefaultItemAnimator (add, remove, move, change) |
| Granular updates | No (only notifyDataSetChanged) | notifyItemInserted/Removed/Changed |
| Click listener | onItemClickListener (built-in) | Via ViewHolder (no built-in) |
| Performance | Adequate for lists up to 50 items | Stable 60 FPS for 1000+ items |
Conclusion: for new projects, use RecyclerView. Keep ListView only in legacy code where migration is unreasonably expensive. According to Material Design 3 guidelines (2026), lists of any complexity should use RecyclerView to meet modern performance and accessibility standards.
Frequently Asked Questions
ListView is not marked as @Deprecated in Android SDK API 35, but Google does not recommend using it in new projects (soft-deprecated). The component remains functional in all Android versions. RecyclerView has been the official replacement since 2014. If you’re starting a new project — choose RecyclerView. If you’re maintaining an existing one — ListView will continue to work without changes.
ListView supports addHeaderView(View, Object, boolean) and addFooterView(View, Object, boolean) methods. Header and footer are added to the general list of items but do not participate in the adapter — they are always displayed at the beginning and end of the list. Important: add header/footer before setting the adapter (setAdapter()), otherwise they will not be taken into account. After adding a header, item position IDs shift by the number of headers.
ListView and ScrollView both handle vertical gestures, which creates a conflict: the outer ScrollView intercepts scrolling, and ListView does not scroll. Solution: (1) don’t nest ListView inside ScrollView — use RecyclerView with NestedScrollingChild, (2) replace ScrollView with NestedScrollView (API 21+), (3) use ListView with a fixed height (android:layout_height="...dp") inside ScrollView, but this breaks responsiveness.
Clearing data in ListView depends on the adapter type. For ArrayAdapter: adapter.clear() and adapter.addAll(newList) followed by adapter.notifyDataSetChanged(). For custom adapter: update the data list and call notifyDataSetChanged(). Note: listView.invalidateViews() only redraws Views but does not update data. Always change data through the adapter, not directly.
ListView item height is set in the item layout XML. If the layout uses layout_height="wrap_content", the height adjusts to the content. For a fixed height of all items, use a custom adapter that sets LayoutParams in getView(): view.layoutParams.height = resources.getDimensionPixelSize(R.dimen.itemHeight). For varying heights, use RecyclerView with different view types.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also