It’s Not Always Sunny In Jetpack Compose: Exploring the Limitations of an Out-of-the-Box Experimental Parallax Toolbar 😤
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 usingTopAppBarDefaults.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 theisCollapsed
property. If thecollapsedFraction
is equal to 1, it setsisCollapsed
totrue
, indicating that the scroll behavior is fully collapsed. Essentially,derivedStateOf
helps 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...
}
— 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
)
- This Jetpack Compose function defines the
LargeTopAppBar
composable found on developer docs. It allows you to add menu actions, a navigation icon (similar to the left chevron in the pic above), and customize its content.
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.
Best,
RC