RecyclerView & ListView

Display scrollable lists efficiently with adapters and the ViewHolder pattern

ListView RecyclerView ViewHolder CardView

Table of Contents

Lists Overview

Android apps constantly show scrollable lists — contacts, messages, products, feeds. Two main widgets handle this: ListView (legacy) and RecyclerView (modern, recommended).

Data List (List<Item>) ↓ Adapter (binds data → views) ↓ ListView / RecyclerView (displays rows) ↓ ViewHolder (caches row view references)
FeatureListViewRecyclerView
View recyclingBasicAdvanced, flexible
Layout managersVertical onlyLinear, Grid, Staggered
AnimationsManualBuilt-in item animations
PerformanceOK for simple listsOptimized for large datasets
RecommendationLegacy / learning onlyUse for all new projects

1ListView Basics

ListView displays a vertically scrollable list of items. It requires an Adapter to supply data and inflate row layouts.

Simple ListView with ArrayAdapter

XML — activity_main.xml<ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="@android:color/darker_gray" android:dividerHeight="1dp" android:padding="8dp" />
Kotlin — Simple ArrayAdapterclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val fruits = listOf("Apple", "Banana", "Cherry", "Date", "Elderberry") val adapter = ArrayAdapter( this, android.R.layout.simple_list_item_1, fruits ) findViewById<ListView>(R.id.listView).adapter = adapter } }

Handle item clicks

Kotlin — ListView item clickfindViewById<ListView>(R.id.listView).setOnItemClickListener { _, _, position, _ -> val selected = fruits[position] Toast.makeText(this, "You picked: $selected", Toast.LENGTH_SHORT).show() }

ListView limitations

  • Only vertical scrolling — no grid or staggered layouts out of the box
  • Recycling is less efficient than RecyclerView for large lists
  • Harder to add item animations and complex row layouts
  • Google recommends RecyclerView for all new development

2Custom Adapter

A custom adapter gives full control over row layout and data binding. Extend BaseAdapter for ListView or RecyclerView.Adapter for RecyclerView.

Data model

Kotlin — Contact.ktdata class Contact( val id: Int, val name: String, val email: String, val avatarRes: Int )

Row layout

XML — item_contact.xml<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" android:gravity="center_vertical"> <ImageView android:id="@+id/ivAvatar" android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/ic_avatar" android:contentDescription="@string/avatar" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginStart="12dp" android:orientation="vertical"> <TextView android:id="@+id/tvName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/tvEmail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="#666666" /> </LinearLayout> </LinearLayout>

Custom BaseAdapter for ListView

Kotlin — ContactListAdapter.ktclass ContactListAdapter( private val context: Context, private val contacts: List<Contact> ) : BaseAdapter() { override fun getCount(): Int = contacts.size override fun getItem(position: Int): Contact = contacts[position] override fun getItemId(position: Int): Long = contacts[position].id.toLong() override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { val view = convertView ?: LayoutInflater.from(context) .inflate(R.layout.item_contact, parent, false) val contact = contacts[position] view.findViewById<TextView>(R.id.tvName).text = contact.name view.findViewById<TextView>(R.id.tvEmail).text = contact.email view.findViewById<ImageView>(R.id.ivAvatar).setImageResource(contact.avatarRes) return view } }

3RecyclerView Setup

RecyclerView is the modern list widget. It separates layout management (LinearLayoutManager, GridLayoutManager) from data binding (Adapter).

Gradle dependency

Kotlin — app/build.gradle.ktsdependencies { implementation("androidx.recyclerview:recyclerview:1.3.2") implementation("androidx.cardview:cardview:1.0.0") }

Layout XML

XML — activity_main.xml<androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" android:padding="8dp" android:scrollbars="vertical" />

Setup in Activity

Kotlin — MainActivity.ktclass MainActivity : AppCompatActivity() { private lateinit var recyclerView: RecyclerView private lateinit var adapter: ContactAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) recyclerView = findViewById(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.setHasFixedSize(true) val contacts = listOf( Contact(1, "Nikhil", "nikhil@example.com", R.drawable.avatar1), Contact(2, "Priya", "priya@example.com", R.drawable.avatar2), Contact(3, "Rahul", "rahul@example.com", R.drawable.avatar3) ) adapter = ContactAdapter(contacts) { contact -> Toast.makeText(this, "Clicked: ${contact.name}", Toast.LENGTH_SHORT).show() } recyclerView.adapter = adapter } }

Layout managers

LayoutManagerLayoutCode
LinearLayoutManagerVertical or horizontal listLinearLayoutManager(context)
GridLayoutManagerGrid of itemsGridLayoutManager(context, 2) (2 columns)
StaggeredGridLayoutManagerPinterest-style masonryStaggeredGridLayoutManager(2, VERTICAL)

4ViewHolder Pattern

The ViewHolder pattern caches references to row views so findViewById is called once per row creation, not on every scroll. RecyclerView requires this pattern in its Adapter.

Why it matters: Without ViewHolder, scrolling a 1000-item list would call findViewById thousands of times, causing jank and battery drain.

ContactAdapter with ViewHolder

Kotlin — ContactAdapter.ktclass ContactAdapter( private val contacts: List<Contact>, private val onItemClick: (Contact) -> Unit ) : RecyclerView.Adapter<ContactAdapter.ContactViewHolder>() { inner class ContactViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val ivAvatar: ImageView = itemView.findViewById(R.id.ivAvatar) val tvName: TextView = itemView.findViewById(R.id.tvName) val tvEmail: TextView = itemView.findViewById(R.id.tvEmail) fun bind(contact: Contact) { ivAvatar.setImageResource(contact.avatarRes) tvName.text = contact.name tvEmail.text = contact.email itemView.setOnClickListener { onItemClick(contact) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_contact, parent, false) return ContactViewHolder(view) } override fun onBindViewHolder(holder: ContactViewHolder, position: Int) { holder.bind(contacts[position]) } override fun getItemCount(): Int = contacts.size }

Adapter lifecycle methods

onCreateViewHolder() → Inflate row layout, create ViewHolder (rare) ↓ onBindViewHolder() → Bind data to ViewHolder views (every visible row) ↓ [User scrolls] ↓ View recycled → Same ViewHolder reused with new data

View Binding in ViewHolder (recommended)

Kotlin — ViewHolder with View Bindinginner class ContactViewHolder( private val binding: ItemContactBinding ) : RecyclerView.ViewHolder(binding.root) { fun bind(contact: Contact) { binding.ivAvatar.setImageResource(contact.avatarRes) binding.tvName.text = contact.name binding.tvEmail.text = contact.email binding.root.setOnClickListener { onItemClick(contact) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder { val binding = ItemContactBinding.inflate( LayoutInflater.from(parent.context), parent, false ) return ContactViewHolder(binding) }

5CardView

CardView wraps list items in a Material card with rounded corners, elevation shadow, and padding — ideal for feeds, product lists, and news cards.

Card item layout

XML — item_product_card.xml<androidx.cardview.widget.CardView 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="wrap_content" android:layout_margin="8dp" app:cardCornerRadius="12dp" app:cardElevation="4dp" app:cardUseCompatPadding="true"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ImageView android:id="@+id/ivProduct" android:layout_width="match_parent" android:layout_height="160dp" android:scaleType="centerCrop" android:contentDescription="@string/product_image" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="12dp"> <TextView android:id="@+id/tvTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/tvPrice" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:textColor="?attr/colorPrimary" android:textSize="18sp" android:textStyle="bold" /> </LinearLayout> </LinearLayout> </androidx.cardview.widget.CardView>

Product model and adapter

Kotlin — Product.kt + ProductAdapter.ktdata class Product(val id: Int, val title: String, val price: Double, val imageRes: Int) class ProductAdapter( private val products: List<Product>, private val onItemClick: (Product) -> Unit ) : RecyclerView.Adapter<ProductAdapter.ProductViewHolder>() { inner class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val ivProduct: ImageView = itemView.findViewById(R.id.ivProduct) private val tvTitle: TextView = itemView.findViewById(R.id.tvTitle) private val tvPrice: TextView = itemView.findViewById(R.id.tvPrice) fun bind(product: Product) { ivProduct.setImageResource(product.imageRes) tvTitle.text = product.title tvPrice.text = "₹${product.price}" itemView.setOnClickListener { onItemClick(product) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ProductViewHolder( LayoutInflater.from(parent.context) .inflate(R.layout.item_product_card, parent, false) ) override fun onBindViewHolder(holder: ProductViewHolder, position: Int) = holder.bind(products[position]) override fun getItemCount() = products.size }

Grid layout with CardView

Kotlin — 2-column product gridrecyclerView.layoutManager = GridLayoutManager(this, 2) recyclerView.adapter = ProductAdapter(productList) { product -> startActivity(Intent(this, ProductDetailActivity::class.java).apply { putExtra("product_id", product.id) }) }

6Dynamic Lists

Dynamic lists change at runtime — add, remove, update, or filter items. Use notifyDataSetChanged() for simple updates, or DiffUtil for efficient animated diffs.

Mutable list with notify methods

Kotlin — Dynamic ContactAdapterclass ContactAdapter( private val contacts: MutableList<Contact>, private val onItemClick: (Contact) -> Unit ) : RecyclerView.Adapter<ContactAdapter.ContactViewHolder>() { // ... ViewHolder code same as before ... fun addContact(contact: Contact) { contacts.add(contact) notifyItemInserted(contacts.size - 1) } fun removeContact(position: Int) { contacts.removeAt(position) notifyItemRemoved(position) } fun updateContact(position: Int, contact: Contact) { contacts[position] = contact notifyItemChanged(position) } fun clearAll() { val size = contacts.size contacts.clear() notifyItemRangeRemoved(0, size) } }

Swipe to delete with ItemTouchHelper

Kotlin — Swipe-to-deleteval swipeHandler = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean = false override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { adapter.removeContact(viewHolder.adapterPosition) Snackbar.make(recyclerView, "Contact deleted", Snackbar.LENGTH_SHORT) .setAction("Undo") { // Re-add deleted item if needed }.show() } } ItemTouchHelper(swipeHandler).attachToRecyclerView(recyclerView)

DiffUtil for efficient updates

Kotlin — DiffUtil with ListAdapterclass ContactDiffCallback : DiffUtil.ItemCallback<Contact>() { override fun areItemsTheSame(old: Contact, new: Contact) = old.id == new.id override fun areContentsTheSame(old: Contact, new: Contact) = old == new } class ContactListAdapter : ListAdapter<Contact, ContactListAdapter.ViewHolder>(ContactDiffCallback()) { // onCreateViewHolder / onBindViewHolder same as before } // Submit new list — DiffUtil calculates minimal changes automatically adapter.submitList(newContactList)

Load data from API (Coroutine pattern)

Kotlin — Fetch and update listlifecycleScope.launch { try { progressBar.visibility = View.VISIBLE val response = apiService.getContacts() adapter.submitList(response.data) } catch (e: Exception) { Toast.makeText(this@MainActivity, "Failed to load", Toast.LENGTH_SHORT).show() } finally { progressBar.visibility = View.GONE } }

7Hands-On Exercises

1

Create a simple ListView with 10 fruit names using ArrayAdapter. Show a Toast with the selected item on click.

2

Build a custom adapter for a contact list with avatar, name, and email in each row.

3

Convert the contact list to RecyclerView with LinearLayoutManager and implement the ViewHolder pattern.

4

Redesign list items using CardView with rounded corners and elevation. Display a product catalog (title, price, image).

5

Switch to GridLayoutManager with 2 columns for the product cards.

6

Add a FAB button that inserts a new contact at the top. Use notifyItemInserted(0) for smooth animation.

7

Implement swipe-to-delete with ItemTouchHelper and an Undo Snackbar.

8

Bonus: Refactor to ListAdapter with DiffUtil. Load contacts from a JSON file or REST API and call submitList().

RecyclerView & ListView MCQ Practice

10 Basic MCQs 10 Advanced MCQs

10 Basic RecyclerView & ListView MCQs

1

RecyclerView replaces ListView because?

ABetter recycling and flexible layout managers
BSmaller APK always
CNo adapter needed
DDeprecated XML
Explanation: RecyclerView decouples layout, animation, and ViewHolder pattern.
2

ViewHolder pattern purpose?

ACache view references per row
BHold Activity instance
CStore APK
DReplace manifest
Explanation: ViewHolder avoids repeated findViewById in scroll.
3

LayoutManager for vertical list?

ALinearLayoutManager
BGridLayout only
CConstraintLayout
DFrameLayoutManager
Explanation: LinearLayoutManager(VERTICAL) is default list layout.
4

Adapter methods include?

AonCreateViewHolder, onBindViewHolder, getItemCount
BonCreate only
ConResume
DsetContentView
Explanation: RecyclerView.Adapter core trio binds data to views.
5

ListView needs adapter like?

AArrayAdapter or BaseAdapter
BNavController
CService
DFragmentManager
Explanation: Adapters map data to row views in ListView.
6

ItemDecoration in RecyclerView?

AAdds dividers/offsets between items
BDeletes items
CSigns APK
DStarts Activity
Explanation: DividerItemDecoration draws separators.
7

notifyDataSetChanged downside?

AInefficient full refresh without diff
BIllegal method
COnly for ListView
DCrashes always
Explanation: Prefer ListAdapter + DiffUtil for granular updates.
8

GridLayoutManager spans?

AMulti-column grid
BOnly one column always
CHorizontal only banned
DServices
Explanation: spanCount sets columns in grid mode.
9

CardView provides?

ARounded corners and elevation shadow
BSQL database
CGPS
DNative HAL
Explanation: Material card container for list item rows.
10

setHasFixedSize(true) when?

ARecyclerView size doesn't depend on adapter item count
BNever
CAlways illegal
DListView only
Explanation: Optimization when RV dimensions are fixed despite data changes.

10 Advanced RecyclerView & ListView MCQs

11

DiffUtil calculates?

AMinimal adapter updates between old/new lists
BDex diff
CGradle diff
DManifest merge
Explanation: DiffUtil dispatches notifyItem* for changed positions only.
12

ListAdapter requires?

ADiffUtil.ItemCallback for compare
BNo ViewHolder
CListView parent
DService binding
Explanation: ListAdapter + DiffUtil.ItemCallback simplify async list updates.
13

RecycledViewPool shares?

AViewHolders across RecyclerViews
BAPK cache
CIntent extras
DPermissions
Explanation: Shared pool improves nested horizontal lists performance.
14

ItemTouchHelper enables?

ASwipe to dismiss and drag reorder
BNetwork refresh
CCamera preview
DGradle sync
Explanation: Attach ItemTouchHelper.Callback for interactive list gestures.
15

ConcatAdapter?

ACombines multiple adapters sequentially
BMerges APKs
CSQL JOIN
DFragment merge
Explanation: Shows headers, sections, footers from separate adapters.
16

Stable IDs in RecyclerView?

AsetHasStableIds helps animation consistency
BRequired always
CBanned
DListView only
Explanation: Stable item IDs improve change animations when enabled.
17

Nested RecyclerView performance tip?

AShare RecycledViewPool, avoid wrap_content height
BDisable scrolling
CUse ListView inside ListView
DRemove ViewHolder
Explanation: Nested RV with same orientation needs careful sizing and pool sharing.
18

Pull to refresh typically with?

ASwipeRefreshLayout wrapping RecyclerView
BProgressBar in manifest
CADB
DIntent filter
Explanation: SwipeRefreshLayout triggers reload onPull.
19

Paging 3 library integrates with?

ARecyclerView via PagingDataAdapter
BListView only
CServices
DHAL
Explanation: Paging loads pages incrementally from network/DB.
20

Empty view pattern for RecyclerView?

AObserve adapter item count toggle empty TextView visibility
BBuilt-in emptyView attribute
CAutomatic always
DGradle property
Explanation: No native emptyView — observe data and show placeholder layout.
Click an option to select, then check answers.

RecyclerView & ListView Interview Q&A

15 topic-focused questions for interviews and revision.

1Why prefer RecyclerView over ListView?easy
Answer: Flexible LayoutManagers, item animations, predictable ViewHolder, ItemDecoration, better performance for complex lists.
2Explain ViewHolder pattern.easy
Answer: onCreateViewHolder inflates row layout into ViewHolder; onBindViewHolder sets data using cached view references.
3Steps to setup RecyclerView.easy
Answer: Add dependency, XML RecyclerView, create Adapter + ViewHolder, set layoutManager and adapter on RecyclerView.
4Difference between ListView and RecyclerView scrolling.medium
Answer: Both scroll; RecyclerView recycles more efficiently and supports horizontal/grid/staggered layouts.
5What is DiffUtil?medium
Answer: Compares old and new lists computing insert/remove/move/change operations for efficient adapter notifications.
6How to add dividers between items?easy
Answer: Add DividerItemDecoration with orientation to RecyclerView.
7CardView in list item layout.easy
Answer: Wrap row content in CardView with margin and cardElevation for Material list appearance.
8Handle item click in RecyclerView.medium
Answer: Set click listener in onBindViewHolder on itemView or use separate interface callback with position.
9Swipe to delete implementation overview.hard
Answer: ItemTouchHelper.SimpleCallback with swipe flags; onSwiped remove item and notify adapter.
10Dynamic list: add item at runtime.medium
Answer: Mutable list in ViewModel, add element, submitList (ListAdapter) or notifyItemInserted.
11Grid vs linear list choice.easy
Answer: Linear for standard feeds; GridLayoutManager for galleries/product grids with spanCount.
12Common mistake: not calling notify after data change.easy
Answer: UI won't update — use appropriate notify* or ListAdapter.submitList.
13RecyclerView wrap_content height issue in ScrollView.hard
Answer: Avoid nesting vertical RV in ScrollView with wrap_content — use NestedScrollView + single adapter or pagination.
14Load images in list with Coil.medium
Answer: imageView.load(url) in onBindViewHolder; Coil cancels previous request on rebind automatically.
15Testing RecyclerView items.hard
Answer: Espresso onView(withId(R.id.recycler)).perform(actionOnItemAtPosition(...)) or IdlingResource.