RecyclerView & ListView
Display scrollable lists efficiently with adapters and the ViewHolder pattern
ListView
RecyclerView
ViewHolder
CardView
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)
Feature ListView RecyclerView
View recycling Basic Advanced, flexible
Layout managers Vertical only Linear, Grid, Staggered
Animations Manual Built-in item animations
Performance OK for simple lists Optimized for large datasets
Recommendation Legacy / learning only Use for all new projects
1 ListView 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 ArrayAdapter class 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 click findViewById<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
2 Custom 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.kt data 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.kt class 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
}
}
3 RecyclerView Setup
RecyclerView is the modern list widget. It separates layout management (LinearLayoutManager, GridLayoutManager) from data binding (Adapter).
Gradle dependency
Kotlin — app/build.gradle.kts dependencies {
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.kt class 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
LayoutManager Layout Code
LinearLayoutManagerVertical or horizontal list LinearLayoutManager(context)
GridLayoutManagerGrid of items GridLayoutManager(context, 2) (2 columns)
StaggeredGridLayoutManagerPinterest-style masonry StaggeredGridLayoutManager(2, VERTICAL)
4 ViewHolder 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.kt class 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 Binding inner 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)
}
5 CardView
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.kt data 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 grid recyclerView.layoutManager = GridLayoutManager(this, 2)
recyclerView.adapter = ProductAdapter(productList) { product ->
startActivity(Intent(this, ProductDetailActivity::class.java).apply {
putExtra("product_id", product.id)
})
}
6 Dynamic 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 ContactAdapter class 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-delete val 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 ListAdapter class 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 list lifecycleScope.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
}
}
7 Hands-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?
A Better recycling and flexible layout managers
B Smaller APK always
C No adapter needed
D Deprecated XML
Explanation: RecyclerView decouples layout, animation, and ViewHolder pattern.
2
ViewHolder pattern purpose?
A Cache view references per row
B Hold Activity instance
C Store APK
D Replace manifest
Explanation: ViewHolder avoids repeated findViewById in scroll.
3
LayoutManager for vertical list?
A LinearLayoutManager
B GridLayout only
C ConstraintLayout
D FrameLayoutManager
Explanation: LinearLayoutManager(VERTICAL) is default list layout.
4
Adapter methods include?
A onCreateViewHolder, onBindViewHolder, getItemCount
B onCreate only
C onResume
D setContentView
Explanation: RecyclerView.Adapter core trio binds data to views.
5
ListView needs adapter like?
A ArrayAdapter or BaseAdapter
B NavController
C Service
D FragmentManager
Explanation: Adapters map data to row views in ListView.
6
ItemDecoration in RecyclerView?
A Adds dividers/offsets between items
B Deletes items
C Signs APK
D Starts Activity
Explanation: DividerItemDecoration draws separators.
7
notifyDataSetChanged downside?
A Inefficient full refresh without diff
B Illegal method
C Only for ListView
D Crashes always
Explanation: Prefer ListAdapter + DiffUtil for granular updates.
8
GridLayoutManager spans?
A Multi-column grid
B Only one column always
C Horizontal only banned
D Services
Explanation: spanCount sets columns in grid mode.
9
CardView provides?
A Rounded corners and elevation shadow
B SQL database
C GPS
D Native HAL
Explanation: Material card container for list item rows.
10
setHasFixedSize(true) when?
A RecyclerView size doesn't depend on adapter item count
B Never
C Always illegal
D ListView only
Explanation: Optimization when RV dimensions are fixed despite data changes.
10 Advanced RecyclerView & ListView MCQs
11
DiffUtil calculates?
A Minimal adapter updates between old/new lists
B Dex diff
C Gradle diff
D Manifest merge
Explanation: DiffUtil dispatches notifyItem* for changed positions only.
12
ListAdapter requires?
A DiffUtil.ItemCallback for compare
B No ViewHolder
C ListView parent
D Service binding
Explanation: ListAdapter + DiffUtil.ItemCallback simplify async list updates.
13
RecycledViewPool shares?
A ViewHolders across RecyclerViews
B APK cache
C Intent extras
D Permissions
Explanation: Shared pool improves nested horizontal lists performance.
14
ItemTouchHelper enables?
A Swipe to dismiss and drag reorder
B Network refresh
C Camera preview
D Gradle sync
Explanation: Attach ItemTouchHelper.Callback for interactive list gestures.
15
ConcatAdapter?
A Combines multiple adapters sequentially
B Merges APKs
C SQL JOIN
D Fragment merge
Explanation: Shows headers, sections, footers from separate adapters.
16
Stable IDs in RecyclerView?
A setHasStableIds helps animation consistency
B Required always
C Banned
D ListView only
Explanation: Stable item IDs improve change animations when enabled.
17
Nested RecyclerView performance tip?
A Share RecycledViewPool, avoid wrap_content height
B Disable scrolling
C Use ListView inside ListView
D Remove ViewHolder
Explanation: Nested RV with same orientation needs careful sizing and pool sharing.
18
Pull to refresh typically with?
A SwipeRefreshLayout wrapping RecyclerView
B ProgressBar in manifest
C ADB
D Intent filter
Explanation: SwipeRefreshLayout triggers reload onPull.
19
Paging 3 library integrates with?
A RecyclerView via PagingDataAdapter
B ListView only
C Services
D HAL
Explanation: Paging loads pages incrementally from network/DB.
20
Empty view pattern for RecyclerView?
A Observe adapter item count toggle empty TextView visibility
B Built-in emptyView attribute
C Automatic always
D Gradle property
Explanation: No native emptyView — observe data and show placeholder layout.
Check All Answers
Click an option to select, then check answers.
RecyclerView & ListView Interview Q&A
15 topic-focused questions for interviews and revision.
1 Why prefer RecyclerView over ListView?easy
Answer: Flexible LayoutManagers, item animations, predictable ViewHolder, ItemDecoration, better performance for complex lists.
2 Explain ViewHolder pattern.easy
Answer: onCreateViewHolder inflates row layout into ViewHolder; onBindViewHolder sets data using cached view references.
3 Steps to setup RecyclerView.easy
Answer: Add dependency, XML RecyclerView, create Adapter + ViewHolder, set layoutManager and adapter on RecyclerView.
4 Difference between ListView and RecyclerView scrolling.medium
Answer: Both scroll; RecyclerView recycles more efficiently and supports horizontal/grid/staggered layouts.
5 What is DiffUtil?medium
Answer: Compares old and new lists computing insert/remove/move/change operations for efficient adapter notifications.
6 How to add dividers between items?easy
Answer: Add DividerItemDecoration with orientation to RecyclerView.
7 CardView in list item layout.easy
Answer: Wrap row content in CardView with margin and cardElevation for Material list appearance.
8 Handle item click in RecyclerView.medium
Answer: Set click listener in onBindViewHolder on itemView or use separate interface callback with position.
9 Swipe to delete implementation overview.hard
Answer: ItemTouchHelper.SimpleCallback with swipe flags; onSwiped remove item and notify adapter.
10 Dynamic list: add item at runtime.medium
Answer: Mutable list in ViewModel, add element, submitList (ListAdapter) or notifyItemInserted.
11 Grid vs linear list choice.easy
Answer: Linear for standard feeds; GridLayoutManager for galleries/product grids with spanCount.
12 Common mistake: not calling notify after data change.easy
Answer: UI won't update — use appropriate notify* or ListAdapter.submitList.
13 RecyclerView wrap_content height issue in ScrollView.hard
Answer: Avoid nesting vertical RV in ScrollView with wrap_content — use NestedScrollView + single adapter or pagination.
14 Load images in list with Coil.medium
Answer: imageView.load(url) in onBindViewHolder; Coil cancels previous request on rebind automatically.
15 Testing RecyclerView items.hard
Answer: Espresso onView(withId(R.id.recycler)).perform(actionOnItemAtPosition(...)) or IdlingResource.