Android Development
ListView
ScrollView
Mobile UI
Android Layout

Android list view inside a scroll view

Master System Design with Codemia

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

Introduction

Putting a ListView inside a ScrollView is a classic Android layout trap. Both views want to handle vertical scrolling, and the parent ScrollView usually measures children with an unspecified height. As a result, the ListView may render only one row, stop scrolling, or perform poorly due to repeated measure passes. The UI seems simple, but the view hierarchy creates conflicting scroll behavior.

Modern Android guidance is clear: avoid nested vertical scrollers of the same direction when possible. A single RecyclerView with multiple view types or a NestedScrollView with carefully controlled nested scrolling is more reliable. If you must keep legacy layouts, you need explicit measurement and behavior control.

Core Sections

1. Prefer a single RecyclerView

The cleanest fix is replacing ScrollView + ListView with one RecyclerView that renders header, list items, and footer as different view types.

kotlin
1class MixedAdapter(
2    private val items: List<Item>
3) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
4
5    override fun getItemCount(): Int = items.size + 2 // header + footer
6
7    override fun getItemViewType(position: Int): Int = when (position) {
8        0 -> 0
9        itemCount - 1 -> 2
10        else -> 1
11    }
12
13    // onCreateViewHolder / onBindViewHolder omitted for brevity
14}

This keeps scroll ownership in one place, improves recycling, and prevents gesture conflicts.

2. If migration is not possible, use NestedScrollView

For transitional screens, replace ScrollView with NestedScrollView, disable child nested scrolling, and set a measured height for the list container.

xml
1<androidx.core.widget.NestedScrollView
2    android:layout_width="match_parent"
3    android:layout_height="match_parent">
4
5    <LinearLayout
6        android:layout_width="match_parent"
7        android:layout_height="wrap_content"
8        android:orientation="vertical">
9
10        <ListView
11            android:id="@+id/legacyList"
12            android:layout_width="match_parent"
13            android:layout_height="wrap_content"
14            android:nestedScrollingEnabled="false"/>
15
16    </LinearLayout>
17</androidx.core.widget.NestedScrollView>

Then compute total list height only for small, bounded datasets.

3. Measure list height carefully for legacy ListView

If item count is limited, you can expand the ListView to full content height so the parent scroll view handles scrolling.

kotlin
1fun setListViewHeightBasedOnChildren(listView: ListView) {
2    val adapter = listView.adapter ?: return
3    var totalHeight = 0
4    for (i in 0 until adapter.count) {
5        val item = adapter.getView(i, null, listView)
6        item.measure(
7            View.MeasureSpec.makeMeasureSpec(listView.width, View.MeasureSpec.AT_MOST),
8            View.MeasureSpec.UNSPECIFIED
9        )
10        totalHeight += item.measuredHeight
11    }
12    val params = listView.layoutParams
13    params.height = totalHeight + (listView.dividerHeight * (adapter.count - 1))
14    listView.layoutParams = params
15}

This is a workaround, not a scalable architecture. For large lists, this defeats view recycling.

4. Verify touch and performance behavior

After layout changes, test gesture handoff and frame times. If fling is janky, profile layout passes and overdraw. Nesting expensive rows in a fully expanded list can cause long initial renders.

Use Android Studio Layout Inspector and adb shell dumpsys gfxinfo to validate improvements.

Common Pitfalls

  • Keeping ScrollView and ListView together for large datasets, which destroys recycling benefits.
  • Expanding list height dynamically on every data change without debouncing, causing layout thrash.
  • Forgetting to disable nested scrolling when using NestedScrollView in compatibility layouts.
  • Measuring rows with wrong width specs, which produces inaccurate total height calculations.
  • Treating the workaround as final architecture instead of migrating to RecyclerView.

Summary

ListView inside ScrollView fails because both controls compete for vertical measurement and scroll ownership. The durable fix is a single RecyclerView with multiple item types. For legacy screens, NestedScrollView plus constrained list-height measurement can stabilize behavior for small datasets, but it is still a compromise. Decide early whether the screen is temporary or long-lived; that decision determines whether a workaround is acceptable or a full RecyclerView migration is required.

A practical way to keep this issue from returning is to turn the fix into a lightweight runbook. Capture the exact environment assumptions (tool versions, runtime flags, cluster or platform settings, and required dependencies), then store a short verification command sequence that any teammate can run from a clean setup. This makes troubleshooting deterministic instead of person-dependent and reduces rework during on-call incidents.

It also helps to add one automated guardrail in CI or pre-deploy checks that validates the critical assumption described above. That guardrail might be a linter rule, a smoke test, a schema check, a policy validation step, or a minimal integration test. When the same class of failure is caught before release, teams spend less time on emergency debugging and more time on controlled improvements.


Course illustration
Course illustration

All Rights Reserved.