Build a Collapsible TopBar in Jetpack Compose Scaffold Easily

Jetpack Compose is revolutionizing Android UI development, offering a modern and declarative approach to building beautiful, responsive, and feature-rich user interfaces. One common UI pattern in mobile apps is the collapsible TopBar, where the app's header dynamically shrinks or expands based on user interactions, such as scrolling. Implementing this pattern in Jetpack Compose is straightforward yet powerful, thanks to the Compose Scaffold and its integration with Modifier and state management.

In this blog post, we’ll dive deep into creating a collapsible TopBar using Jetpack Compose. This guide is designed for intermediate to advanced Android developers who are familiar with Compose basics and want to explore advanced UI concepts. Let’s get started!

Table of Contents

  1. Understanding the Collapsible TopBar Pattern

  2. Setting Up the Project

  3. Key Concepts in Jetpack Compose for Scrollable UIs

  4. Implementing a Basic Scaffold Layout

  5. Adding Scrollable Content with LazyColumn

  6. Building the Collapsible TopBar

  7. Advanced Customizations and Best Practices

  8. Performance Optimization Tips

1. Understanding the Collapsible TopBar Pattern

A collapsible TopBar improves the user experience by conserving vertical screen space when users scroll through content. The header might shrink to a smaller version or completely hide while scrolling down and reappear upon scrolling up.

This pattern is prevalent in apps like Google Photos, Twitter, and Instagram. The key benefits include:

  • Enhanced content focus by minimizing distractions.

  • Improved usability for content-heavy screens.

  • A polished, professional look that aligns with modern design standards.

Components of a Collapsible TopBar

  • AppBar: The primary header that contains titles, icons, or actions.

  • Content Area: A scrollable list or layout (e.g., LazyColumn).

  • Scroll Behavior: Logic that determines how the TopBar reacts to user scrolls.

2. Setting Up the Project

To get started, ensure your Android project is configured for Jetpack Compose. Update your build.gradle files with the required dependencies:

def compose_version = "1.4.0"

dependencies {
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.material:material:$compose_version"
    implementation "androidx.compose.ui:ui-tooling:$compose_version"
    implementation "androidx.compose.foundation:foundation:$compose_version"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
    implementation "androidx.activity:activity-compose:1.7.0"
}

Make sure you’re using Android Studio Flamingo or later for optimal Compose support.

3. Key Concepts in Jetpack Compose for Scrollable UIs

Jetpack Compose offers several tools to create scrollable and interactive UIs:

  • Modifier.nestedScroll: A powerful modifier that helps link scrolling behavior between parent and child components.

  • LazyColumn and LazyRow: Compose’s efficient list components for large, scrollable datasets.

  • rememberScrollState: Manages scroll offsets and provides a state object for custom behaviors.

  • TopAppBar: A ready-made Material Design component for app bars, which can be customized extensively.

4. Implementing a Basic Scaffold Layout

The Scaffold in Jetpack Compose provides a flexible structure for building layouts with standard components like TopBars, BottomBars, and FABs. Here’s how to set up a simple Scaffold:

@Composable
fun CollapsibleTopBarDemo() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Collapsible TopBar") },
                backgroundColor = MaterialTheme.colorScheme.primary
            )
        }
    ) { innerPadding ->
        Content(innerPadding)
    }
}

@Composable
fun Content(padding: PaddingValues) {
    LazyColumn(contentPadding = padding) {
        items(50) { index ->
            Text(
                text = "Item #$index",
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            )
        }
    }
}

This creates a simple UI with a static TopBar and scrollable content.

5. Adding Scrollable Content with LazyColumn

The LazyColumn is the backbone of many scrollable UIs in Compose. Its lazy-loading mechanism ensures optimal performance, even with large datasets. To integrate it into the Scaffold:

  1. Define your items using the items function.

  2. Add padding to handle the Scaffold’s inner padding.

The LazyColumn will be our canvas for implementing the collapsible TopBar.

6. Building the Collapsible TopBar

The magic of the collapsible TopBar lies in its dynamic response to scroll states. Let’s implement this step by step:

Step 1: Create a ScrollState

val scrollState = rememberLazyListState()

Step 2: Calculate TopBar Height

Use the scroll offset to determine the TopBar’s height. The height will shrink as the user scrolls:

val topBarHeight by derivedStateOf {
    val maxOffset = 200f
    val scrollOffset = min(scrollState.firstVisibleItemScrollOffset, maxOffset.toInt())
    maxOffset - scrollOffset
}

Step 3: Dynamic TopBar

@Composable
fun CollapsibleTopBar(scrollState: LazyListState) {
    val topBarHeight = derivedStateOf {
        val maxHeight = 200.dp
        val minHeight = 56.dp
        val offset = scrollState.firstVisibleItemScrollOffset
        val height = maxHeight - (offset / 10).dp
        max(height, minHeight)
    }

    Box(
        modifier = Modifier
            .fillMaxWidth()
            .height(topBarHeight.value)
            .background(MaterialTheme.colorScheme.primary)
    ) {
        Text(
            text = "Collapsible TopBar",
            style = MaterialTheme.typography.h6,
            color = Color.White,
            modifier = Modifier.align(Alignment.Center)
        )
    }
}

Step 4: Integrate with Scaffold

Scaffold(
    topBar = {
        CollapsibleTopBar(scrollState)
    }
) { innerPadding ->
    LazyColumn(
        state = scrollState,
        contentPadding = innerPadding
    ) {
        items(50) { index ->
            Text(
                text = "Item #$index",
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            )
        }
    }
}

7. Advanced Customizations and Best Practices

  • Animate Height Changes: Use animateDpAsState for smooth transitions.

  • Persist Scroll State: Save and restore scroll positions using rememberSaveable.

  • Integrate Parallax Effects: Add background images or gradients that move at a different pace from the TopBar.

Example:

val animatedHeight by animateDpAsState(targetValue = topBarHeight)

8. Performance Optimization Tips

  • Avoid Over-Rendering: Use remember and derivedStateOf to recompute values only when needed.

  • Test on Low-End Devices: Ensure smooth performance across various screen sizes and hardware capabilities.

  • Profile Your App: Use tools like Android Studio’s profiler to monitor frame rendering and memory usage.

Conclusion

Creating a collapsible TopBar in Jetpack Compose is both intuitive and flexible. By leveraging Compose’s powerful state management and layout modifiers, you can build dynamic, engaging UIs with minimal effort. The approach discussed here can be further extended to include animations, parallax effects, and other advanced behaviors.

Start implementing collapsible TopBars in your apps today, and elevate the user experience with modern, polished UI patterns!