It’s Not Always Sunny In Jetpack Compose: Exploring the Limitations of an Out-of-the-Box Experimental Parallax Toolbar 😤

Raphael Cohen
3 min readJul 27, 2024

--

Team Colorado Avalanche Parallax Toolbar in Jetpack Compose… what else can I say y’all, I ❤️ hockey!

Introduction

The Parallax toolbar is a UI effect that combines scrolling behavior with a fixed app bar. In this blog, we’ll explore how to implement a collapsing toolbar with a parallax effect using Jetpack Compose. We’ll break down the code snippet and explain each part in detail.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ScheduleScreen(
navController: NavController,
viewModel: HomeViewModel,
uiState: TeamScheduleUiState.Success,
) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
val isCollapsed: Boolean by remember {
derivedStateOf { scrollBehavior.state.collapsedFraction == 1f }
}
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
ParallaxToolBar(
scrollBehavior = scrollBehavior,
navController = navController,
title = viewModel.getToolBarTitle(isCollapsed, uiState)
color = uiState.team.teamColor,
actions = {
IconButton(
onClick = { navController.navigate(ScreenDestination.Profile.route) }
) {
Icon(
painter = painterResource(id = R.drawable.profile)
)
}
}
)
}
) { padding ->
LazyColumn(Modifier.padding(padding)
) {
item { TeamScheduleCard(homeViewModel, uiState) }
}
}
}

Code Breakdown

Let’s dissect the provided code snippet step by step:

— Scroll Behavior Initialization —

val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
  • We create a scrollBehavior instance using TopAppBarDefaults.enterAlwaysScrollBehavior.
  • This behavior ensures that the top app bar remains visible even when the user scrolls down the content.
  • Do check out TopAppBarDefaults.exitAlwaysScrollBehavior, imho, this behavior seems to be the must fluid UX experience. 👍🏽

— Collapsed State Check —

val isCollapsed: Boolean by remember {
derivedStateOf { scrollBehavior.state.collapsedFraction == 1f }
}
  • We use derivedStateOf to compute the isCollapsed property. If the collapsedFraction is equal to 1, it sets isCollapsed to true, indicating that the scroll behavior is fully collapsed. Essentially, derivedStateOfhelps optimize recompositions by avoiding unnecessary calculations when the source state doesn’t change frequently.
  • If collapsedFraction (the fraction of app bar collapse) is equal to 1, it means the app bar is fully expanded.

Scaffold Composable

Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
// Other parameters...
) { padding ->
// Content composable...
}
We apply the nestedScroll modifier to allow nested scrolling behavior.💥

— Composable Function Signature —

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ParallaxToolBar(
scrollBehavior: TopAppBarScrollBehavior,
navController: NavController,
title: String,
color: String,
actions: @Composable () -> Unit
) = LargeTopAppBar(
title = { Text(title) },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Icons.AutoMirrored.Default.KeyboardArrowLeft)
}
},
colors = TopAppBarDefaults.largeTopAppBarColors(containerColor = color),
actions = { actions() },
scrollBehavior = scrollBehavior
)

Limitations ⚠️

  • It seems as though the fixed height of the parallax toolbar restricts its ability to load dynamic images or any content with dynamic height. And this is open to interpretation, but the LargeTopAppBar is entirely too small in height, leaving me with a ‘what’s the point?’ for the parallax 🤷‍♀. Even best case scenario you’re on a compact device — it’s still too small.
  • Keep in mind that this feature is experimental and may evolve.

Summary

The given code accomplishes its intended functionality. However, it’s crucial to recognize its limitations. Design teams and engineer discussions may have this on their radar that Jetpack Compose, while powerful, may not offer the same flexibility as the older more traditional approach of using CollapsingToolbarLayout or CoordinatorLayout for parallax behavior. I mean you can always build out your own ToolBar and create a homegrown parallax — my point is, I feel like we should not have to do this.

For some inspiration, explore the example app I built on Google Play that showcase Parallax features and extensive Jetpack Compose usage! And please leave a review if time permits, it helps with app placement. 🎨 😊

🗣️: reach out on X | Insta

Best,
RC

--

--