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)
| Concept | What It Means | Why 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 / Pattern | Meaning | Tip |
|---|---|---|
| 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.