Hide the App Bar on Scroll with Jetpack Compose

Jetpack Compose, Google’s modern UI toolkit for Android, has transformed how developers create user interfaces. Among its many powerful features, implementing interactive components like hiding the app bar on scroll is both streamlined and flexible. This guide dives deep into how to achieve this behavior effectively using Jetpack Compose, covering best practices, advanced techniques, and performance considerations.

Why Hide the App Bar on Scroll?

Hiding the app bar during scrolling is a popular design pattern, particularly for content-heavy applications like news readers, e-commerce apps, and social media platforms. This approach:

  • Maximizes screen space, providing users more room to view content.

  • Enhances the user experience by dynamically responding to user interactions.

  • Aligns with Material Design guidelines, promoting clean and modern app aesthetics.

Overview of the Implementation

Hiding the app bar on scroll in Jetpack Compose involves:

  1. Tracking the scroll state of the content.

  2. Animating the visibility of the app bar based on the scroll state.

  3. Ensuring smooth transitions and optimal performance.

To achieve this, we’ll use the LazyColumn for scrollable content and a combination of Modifier, rememberScrollState, and animateFloatAsState for tracking and animating the app bar.

Prerequisites

Before we begin, ensure your project is set up with:

  • Jetpack Compose version 1.2.0 or higher.

  • Kotlin 1.7.0 or higher.

  • Material 3 components (optional but recommended for modern design).

Step 1: Setting Up the Scaffold

The Scaffold composable provides a convenient structure for layouts with app bars, floating action buttons, and content areas. Start by creating a basic scaffold layout:

@Composable
fun HideAppBarOnScroll() {
    val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()

    Scaffold(
        topBar = {
            LargeTopAppBar(
                title = { Text("Hide on Scroll") },
                scrollBehavior = scrollBehavior
            )
        },
        content = { paddingValues ->
            ContentList(Modifier.padding(paddingValues))
        }
    )
}

Here, exitUntilCollapsedScrollBehavior() handles the interaction between the app bar and scrolling content.

Step 2: Creating the Content List

A LazyColumn is ideal for scrollable content. Define a sample list:

@Composable
fun ContentList(modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier.fillMaxSize()) {
        items(50) { index ->
            Text(
                text = "Item #$index",
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                style = MaterialTheme.typography.body1
            )
        }
    }
}

The LazyColumn efficiently renders only visible items, making it perfect for performance.

Step 3: Animating the App Bar

While the scrollBehavior simplifies scroll interactions, you can create a custom animation to control the app bar visibility based on scroll state. To do this, we’ll manually track the scroll state and use animateFloatAsState for smooth transitions:

@Composable
fun CustomHideAppBarOnScroll() {
    val scrollState = rememberLazyListState()
    val appBarOffset by remember { derivedStateOf { scrollState.firstVisibleItemScrollOffset } }
    val appBarAlpha by animateFloatAsState(targetValue = if (appBarOffset > 0) 0f else 1f)

    Column {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .background(MaterialTheme.colorScheme.primary)
                .height(56.dp * appBarAlpha)
        ) {
            Text(
                text = "Custom App Bar",
                color = MaterialTheme.colorScheme.onPrimary,
                modifier = Modifier.align(Alignment.Center)
            )
        }

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

This approach allows granular control over the app bar’s appearance.

Step 4: Optimizing for Performance

While Compose handles rendering efficiently, there are key practices to ensure optimal performance:

  • Avoid Excessive Recomposition: Use derivedStateOf to compute derived values, reducing unnecessary recompositions.

  • Leverage remember: Cache states and calculations to avoid redundant computations.

  • Test with Large Data Sets: Simulate realistic scenarios with many items to identify potential bottlenecks.

Advanced Customizations

To elevate the user experience, consider adding:

Parallax Effect

Create a parallax effect for the app bar background:

@Composable
fun ParallaxAppBar(scrollState: LazyListState) {
    val offset by remember { derivedStateOf { scrollState.firstVisibleItemScrollOffset / 2f } }

    Box(
        modifier = Modifier
            .fillMaxWidth()
            .height(200.dp)
            .graphicsLayer { translationY = -offset }
            .background(MaterialTheme.colorScheme.primary)
    ) {
        Text(
            text = "Parallax Header",
            color = MaterialTheme.colorScheme.onPrimary,
            modifier = Modifier.align(Alignment.Center)
        )
    }
}

Integrate this within the scaffold layout for a dynamic visual effect.

Collapsing App Bar

For a collapsing toolbar effect, use scrollBehavior:

val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
LargeTopAppBar(
    title = { Text("Collapsible App Bar") },
    scrollBehavior = scrollBehavior
)

Sticky Headers

Add sticky headers to your list for better content organization:

LazyColumn(state = scrollState) {
    stickyHeader {
        Text(
            text = "Sticky Header",
            modifier = Modifier
                .fillMaxWidth()
                .background(MaterialTheme.colorScheme.surface)
                .padding(16.dp),
            style = MaterialTheme.typography.h6
        )
    }
    items(50) { index ->
        Text(
            text = "Item #$index",
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        )
    }
}

Conclusion

Jetpack Compose simplifies implementing dynamic UI patterns like hiding the app bar on scroll. By leveraging its powerful state management and animation capabilities, you can create engaging, responsive designs that enhance user experience.

Experiment with the techniques covered here to tailor the behavior to your app’s unique needs. With Compose’s growing ecosystem, the possibilities for crafting delightful interfaces are endless.

Happy coding!