🎨 UI – Views & Layouts · Lesson 16
ScrollView, CardView & Material Components
ScrollView and NestedScrollView
When content is taller than the screen, wrap it in a scroll container. Use NestedScrollView (AndroidX) — it works well with toolbars that collapse and with CoordinatorLayout.
XML
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<!-- exactly ONE child -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- many views here: text, images, cards, buttons … -->
</LinearLayout>
</androidx.core.widget.NestedScrollView>HorizontalScrollView
XML – scrolling chips
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true">
<!-- chips … -->
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>MaterialCardView
XML – course card
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardCourse"
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="160dp"
android:contentDescription="@null"
android:scaleType="centerCrop"
android:src="@drawable/course_android" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="Android with Kotlin"
android:textAppearance="?attr/textAppearanceTitleLarge" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>| Style | Look |
|---|---|
| Widget.Material3.CardView.Elevated | Shadow |
| Widget.Material3.CardView.Filled | Tinted background, no shadow |
| Widget.Material3.CardView.Outlined | Border |
Material buttons
XML – button styles
<!-- filled (main action) -->
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Enroll now" app:icon="@drawable/ic_add" />
<!-- tonal (secondary) -->
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Add to wishlist" />
<!-- outlined -->
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Syllabus" />
<!-- text button (least important) -->
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Share" />Floating Action Button
XML + Kotlin
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fabEnroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:text="Enroll"
app:icon="@drawable/ic_add" />
// Kotlin – shrink to a round FAB when the user scrolls down
binding.scroll.setOnScrollChangeListener { _, _, y, _, oldY ->
if (y > oldY) binding.fabEnroll.shrink() else binding.fabEnroll.extend()
}Progress indicators
XML + Kotlin
<!-- spinner while loading -->
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/loader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
<!-- course progress bar -->
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressCourse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:trackCornerRadius="6dp"
app:trackThickness="10dp" />
// Kotlin
binding.progressCourse.setProgressCompat(65, true) // animate to 65%
binding.loader.isVisible = falseExample: course detail screen
Common mistakes
- Two children inside a ScrollView → crash "ScrollView can host only one direct child".
- Child height
match_parentinside ScrollView → usewrap_content(andfillViewportfor short content). - Using the old
android.widget.Buttonstyles mixed with Material styles → inconsistent look; use MaterialButton everywhere (the theme does this automatically for <Button>).
Summary
- NestedScrollView with exactly one child for long screens.
- MaterialCardView for grouped content; Material button styles show importance.
- FAB for the main action; Circular/Linear progress indicators for loading and progress.
✅ Practice
Build the course detail screen above: banner, title, 5 week cards, a progress bar at 40% and an extended FAB that shrinks when scrolling down.
Frequently Asked Questions
Why can a ScrollView have only one child?
ScrollView scrolls a single content view. Put a LinearLayout or ConstraintLayout inside it and place all your views in that layout.
Should I put a RecyclerView inside a ScrollView?
Avoid it — the RecyclerView loses its recycling and becomes slow. Use one RecyclerView with different view types, or a ConcatAdapter.
CardView or MaterialCardView?
Use MaterialCardView from the Material library. It supports Material 3 styles, strokes, checkable cards and theme colours.
