Create a Sticky App Bar in Jetpack Compose

Jetpack Compose has revolutionized Android development by offering a modern, declarative approach to building user interfaces. One of the most sought-after UI patterns in mobile app design is the sticky app bar, where a header or app bar remains at the top of the screen even as users scroll through content. This blog post explores how to create a sticky app bar in Jetpack Compose, diving into both the basics and advanced customization options.

What is a Sticky App Bar?

A sticky app bar, often referred to as a pinned header, is a UI component that stays fixed at the top of the screen while users scroll through other content. This feature is particularly useful for improving navigation and maintaining context in apps with long, scrollable content.

In Jetpack Compose, achieving this functionality involves understanding key components like LazyColumn, Modifier, and ScrollableState.

Benefits of a Sticky App Bar

  • Improved Usability: Ensures navigation options or key information are always visible.

  • Enhanced Design: Adds a polished and professional look to your app.

  • Better Context: Keeps users aware of their current screen or section.

Step-by-Step Guide to Creating a Sticky App Bar

1. Setting Up the Project

Before diving into code, ensure your project is set up to use Jetpack Compose. Add the necessary dependencies in your build.gradle file:

def compose_version = "1.5.0"
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"

2. Basic Layout with LazyColumn

The LazyColumn is the go-to component for implementing vertical scrolling lists in Jetpack Compose. It supports composable headers, making it perfect for creating a sticky app bar.

@Composable
fun StickyAppBarExample() {
    LazyColumn {
        stickyHeader {
            AppBar("Sticky Header")
        }
        items(50) { index ->
            ListItem(index)
        }
    }
}

@Composable
fun AppBar(title: String) {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .background(MaterialTheme.colors.primary)
            .padding(16.dp)
    ) {
        Text(
            text = title,
            color = Color.White,
            style = MaterialTheme.typography.h6
        )
    }
}

@Composable
fun ListItem(index: Int) {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(text = "Item #$index")
    }
}

Key Takeaways:

  1. stickyHeader {}: A special function in LazyColumn that ensures the composable remains fixed at the top.

  2. Performance: LazyColumn optimizes performance by lazily rendering items as they appear on the screen.

Advanced Customizations

While the basic implementation works, real-world apps often require additional features like animations, dynamic styling, and integrating multiple sticky headers. Here’s how you can enhance your sticky app bar.

1. Adding Animations

To make your sticky app bar visually engaging, you can add animations that respond to scroll state. For instance, shrinking the app bar as the user scrolls:

@Composable
fun AnimatedStickyAppBar() {
    val scrollState = rememberLazyListState()
    val appBarHeight = (56 - (scrollState.firstVisibleItemScrollOffset / 10)).coerceIn(40, 56)

    LazyColumn(state = scrollState) {
        stickyHeader {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(appBarHeight.dp)
                    .background(MaterialTheme.colors.primary)
                    .padding(16.dp)
            ) {
                Text(
                    text = "Animated Sticky Header",
                    color = Color.White,
                    style = MaterialTheme.typography.h6
                )
            }
        }
        items(50) { index ->
            ListItem(index)
        }
    }
}

Key Concepts:

  • LazyListState: Captures the scroll state of the list.

  • Dynamic Heights: Use coerceIn to maintain reasonable height limits for the app bar.

2. Multiple Sticky Headers

For apps requiring multiple sections with sticky headers:

@Composable
fun MultiSectionList() {
    val sections = listOf("Section 1" to 10, "Section 2" to 20, "Section 3" to 15)

    LazyColumn {
        sections.forEach { (section, itemCount) ->
            stickyHeader {
                AppBar(section)
            }
            items(itemCount) { index ->
                ListItem(index)
            }
        }
    }
}

This approach groups items under specific headers, keeping them sticky as you scroll.

3. Custom Scroll Behaviors

Using NestedScrollConnection, you can create highly customized scroll behaviors, such as parallax effects or synchronized scrolling between multiple components:

val scrollBehavior = remember {
    object : NestedScrollConnection {
        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
            // Implement custom scroll logic
            return Offset.Zero
        }
    }
}

Box(
    modifier = Modifier.nestedScroll(scrollBehavior)
) {
    // Your scrollable content
}

Best Practices

1. Optimize Performance

  • Use LazyColumn for lists instead of Column to avoid performance issues.

  • Keep sticky headers lightweight to minimize rendering overhead.

2. Accessibility

  • Add content descriptions to ensure your sticky app bar is accessible to screen readers.

Text(
    text = title,
    color = Color.White,
    style = MaterialTheme.typography.h6,
    modifier = Modifier.semantics { contentDescription = "Sticky Header: $title" }
)

3. Consistency with Material Design

  • Leverage Material Design components for a polished look.

  • Use MaterialTheme for colors, typography, and shapes.

Conclusion

Creating a sticky app bar in Jetpack Compose is straightforward yet powerful. By leveraging LazyColumn and its stickyHeader capability, you can implement robust and visually appealing UI patterns. With advanced customizations like animations, multiple sticky headers, and custom scroll behaviors, your app can stand out in terms of both design and functionality.

Jetpack Compose continues to empower developers with its declarative approach, and mastering concepts like sticky app bars can significantly elevate your app’s user experience.

Have you implemented sticky app bars in your projects? Share your experiences and tips in the comments below!