ScrollView (Java) — One-Child Rule, fillViewPort & Smooth Scrolling

ScrollView provides vertical scrolling for content taller than the screen. It accepts only one direct child. For multiple items, put a container (usually LinearLayout (vertical) or ConstraintLayout) inside it. For horizontal scrolling, use HorizontalScrollView.

Core Concepts (Cheat Sheet)

ConceptWhat It MeansWhy It Matters
One Direct Child ScrollView can host only a single child view Wrap multiple elements inside a container
android:fillViewport Child stretches to at least viewport height Handy to pin buttons to bottom when content is short
Nested Scrolling Avoid putting scrollables inside ScrollView Use RecyclerView for lists; use NestedScrollView for app bars
Smooth Scrolling Programmatically scroll to a view/position Guide users to errors or sections

Mental Diagram

ScrollView → One container (LinearLayout/ConstraintLayout) → Many child views (TextView, EditText, Button, …)
ScrollView handles vertical scroll for the entire container.

Example 1 — Basic Vertical Form (One-Child Rule)

<!-- res/layout/sv_basic_form.xml -->
<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <!-- SINGLE direct child (container) -->
  <LinearLayout
      android:orientation="vertical"
      android:padding="16dp"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

      <TextView android:text="Sign Up" android:textStyle="bold" />
      <EditText android:hint="Full Name" />
      <EditText android:hint="Email" android:inputType="textEmailAddress" />
      <EditText android:hint="Password" android:inputType="textPassword" />
      <Button android:text="Create account" />

  </LinearLayout>
</ScrollView>
Why: The ScrollView has exactly one direct child (LinearLayout). All form elements sit inside that container.

Example 2 — fillViewPort to Keep Button at Bottom (until overflow)

<!-- res/layout/sv_fill_viewport.xml -->
<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:fillViewport="true"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <LinearLayout
      android:orientation="vertical"
      android:padding="16dp"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <TextView android:text="Short content" />
      <View
          android:layout_width="match_parent"
          android:layout_height="0dp"
          android:layout_weight="1" />

      <Button
          android:text="Continue"
          android:layout_width="match_parent"
          android:layout_height="wrap_content" />

  </LinearLayout>
</ScrollView>

Why: fillViewport="true" lets the inner layout fill screen height; the spacer (0dp + weight=1) pushes the button to the bottom until content grows taller than the viewport.

Example 3 — HorizontalScrollView (thumbnails)

<!-- res/layout/hsv_gallery.xml -->
<HorizontalScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:fillViewport="true">

  <LinearLayout
      android:orientation="horizontal"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content">

      <ImageView android:src="@drawable/p1" />
      <ImageView android:src="@drawable/p2" />
      <ImageView android:src="@drawable/p3" />

  </LinearLayout>
</HorizontalScrollView>

Example 4 — Programmatic Scroll & Scroll Listener (Java)

// In Activity/Fragment
import android.view.View;
import android.widget.ScrollView;
import android.os.Build;

ScrollView sv = findViewById(R.id.scrollView);
View errorField = findViewById(R.id.etEmail);

// Smooth scroll to a specific child
sv.post(() -> sv.smoothScrollTo(0, errorField.getTop()));

// Listen to scroll changes (API 23+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  sv.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
    if (scrollY > 150) {
      // e.g., elevate toolbar or lazy-load
    }
  });
}

Example 5 — NestedScrollView (when integrating with AppBar/CollapsingToolbar)

If you’re using CoordinatorLayout + AppBarLayout (material collapsing toolbar), prefer NestedScrollView over plain ScrollView for proper nested scrolling behavior.

<!-- res/layout/nsv_with_appbar.xml (simplified) -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <com.google.android.material.appbar.AppBarLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content">
      <!-- CollapsingToolbarLayout + Toolbar here -->
  </com.google.android.material.appbar.AppBarLayout>

  <androidx.core.widget.NestedScrollView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_behavior="@string/appbar_scrolling_view_behavior">

      <LinearLayout
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="wrap_content">
          <!-- Long content here -->
      </LinearLayout>

  </androidx.core.widget.NestedScrollView>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Example 6 — Don’t wrap RecyclerView inside ScrollView

Anti-pattern: Putting RecyclerView inside a ScrollView leads to poor performance and scroll conflicts. Use RecyclerView alone or redesign with NestedScrollView only if truly required.

Example 7 — Keyboard Focus & Scrolling to Field

Scrolling forms should keep the focused field visible when the keyboard opens.

<!-- In AndroidManifest.xml, per Activity -->
<activity
    android:name=".FormActivity"
    android:windowSoftInputMode="adjustResize" />
// In Java: request focus & scroll
EditText et = findViewById(R.id.etAddress);
et.requestFocus();
sv.post(() -> sv.smoothScrollTo(0, et.getBottom()));

ScrollView Properties & Tips

Property / PatternMeaningTip
android:fillViewport="true" Child fills screen height Good for bottom buttons on short content
descendantFocusability How child focus is handled Default is fine; use wisely if custom focus jumps
Programmatic scrolling scrollTo / smoothScrollTo Guide users to errors
Don’t nest scrollables Avoid ScrollView inside ScrollView/RecyclerView Use RecyclerView for long lists
Performance Prefer wrap_content heights inside ScrollView Avoid match_parent height on children

Example 8 — Settings Screen Template

<!-- res/layout/sv_settings.xml -->
<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:fillViewport="true"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <LinearLayout
      android:orientation="vertical"
      android:padding="12dp"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <TextView android:text="General" android:textStyle="bold" />
      <Switch android:text="Notifications" />
      <Switch android:text="Dark Mode" />

      <TextView
          android:text="Account"
          android:layout_marginTop="16dp"
          android:textStyle="bold" />

      <Button
          android:text="Sign out"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_marginTop="12dp" />

      <!-- Spacer to push footer button down if desired -->
      <View android:layout_height="0dp" android:layout_weight="1" />

      <Button android:text="Save Changes" />

  </LinearLayout>
</ScrollView>

Gotchas & Best Practices

  • Only one direct child: If you get a warning, you likely put multiple children directly under ScrollView—wrap them.
  • Use RecyclerView for lists: It recycles item views; ScrollView + many children wastes memory.
  • Keyboard overlap: Use adjustResize to ensure fields aren’t hidden when typing.
  • Avoid fixed huge heights: Inside ScrollView, children usually should be wrap_content in height.
  • Accessibility: Maintain logical focus order; ensure labels for form fields.