Horizontal ListView
RecyclerView Guide
Android Development
UI Design
Mobile App Tutorial

How to build a horizontal ListView with RecyclerView

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

On modern Android, a horizontal list is built with RecyclerView, not the old ListView widget. The key is a horizontal LinearLayoutManager, but a good solution also handles spacing, item updates, and recycled view state correctly. If you only flip the orientation and ignore the rest, the list works at first and then becomes hard to maintain.

Configure the RecyclerView

The baseline setup is small: give the view a layout manager, an adapter, and fixed sizing if item height is stable.

kotlin
1class MainActivity : AppCompatActivity() {
2    override fun onCreate(savedInstanceState: Bundle?) {
3        super.onCreate(savedInstanceState)
4        setContentView(R.layout.activity_main)
5
6        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
7        recyclerView.layoutManager =
8            LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
9        recyclerView.adapter = NameAdapter(listOf("One", "Two", "Three"))
10        recyclerView.setHasFixedSize(true)
11    }
12}

That gives you horizontal scrolling immediately.

A Simple Adapter

kotlin
1class NameAdapter(private val items: List<String>) :
2    RecyclerView.Adapter<NameAdapter.NameViewHolder>() {
3
4    class NameViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
5        val title: TextView = itemView.findViewById(R.id.itemTitle)
6    }
7
8    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NameViewHolder {
9        val view = LayoutInflater.from(parent.context)
10            .inflate(R.layout.item_name, parent, false)
11        return NameViewHolder(view)
12    }
13
14    override fun onBindViewHolder(holder: NameViewHolder, position: Int) {
15        holder.title.text = items[position]
16    }
17
18    override fun getItemCount(): Int = items.size
19}

This is enough for a working prototype. For frequently changing data, move to ListAdapter plus DiffUtil so updates stay efficient.

Add Spacing the Right Way

Horizontal rows usually need spacing between items. Do that with ItemDecoration instead of hardcoding margins into every layout.

kotlin
1class HorizontalSpacing(private val px: Int) : RecyclerView.ItemDecoration() {
2    override fun getItemOffsets(
3        outRect: Rect,
4        view: View,
5        parent: RecyclerView,
6        state: RecyclerView.State
7    ) {
8        outRect.right = px
9        if (parent.getChildAdapterPosition(view) == 0) {
10            outRect.left = px
11        }
12    }
13}

Then attach it:

kotlin
recyclerView.addItemDecoration(HorizontalSpacing(16))

This keeps spacing logic separate from view binding.

Keep UI State Out of Recycled Views

A classic RecyclerView bug is storing selected state only in the clicked view. When the holder is recycled, the wrong item appears selected.

Track selection in adapter state or in the backing model, then rebind from that state in onBindViewHolder. That is the correct mental model for RecyclerView: views are temporary, data state is authoritative.

Plan for Real Data Updates

Many horizontal lists start as static mock data and then become live content rails. When that happens, switch to ListAdapter plus DiffUtil instead of calling notifyDataSetChanged() for every refresh. Diff-based updates keep animations smooth and preserve scroll position more reliably, which matters a lot in horizontal carousels where a jarring reset is obvious immediately.

Layout XML Matters Too

A clean container layout helps the horizontal list feel intentional.

xml
1<androidx.recyclerview.widget.RecyclerView
2    android:id="@+id/recyclerView"
3    android:layout_width="match_parent"
4    android:layout_height="120dp"
5    android:clipToPadding="false"
6    android:paddingStart="8dp"
7    android:paddingEnd="8dp" />

For card rails, consistent item width usually produces smoother scrolling. Uneven widths are valid, but they should be a deliberate design choice.

If the row displays remote images, pair the adapter with Glide or Coil so image decoding and caching do not block horizontal scrolling.

Common Pitfalls

  • Forgetting to set LinearLayoutManager.HORIZONTAL, leaving the list vertical.
  • Using notifyDataSetChanged for every update instead of diff-based updates.
  • Putting spacing into every item layout instead of using ItemDecoration.
  • Storing interaction state only in the view holder and then fighting recycled-state bugs.

Summary

  • Use RecyclerView with a horizontal LinearLayoutManager for modern horizontal lists.
  • Start with a simple adapter, then use DiffUtil when data updates matter.
  • Add spacing with ItemDecoration instead of scattered layout margins.
  • Keep selection and other UI state in data, not only in recycled views.
  • Treat RecyclerView as a reusable rendering system, not as a manual row container.

Course illustration
Course illustration

All Rights Reserved.