Skip to content

Instantly share code, notes, and snippets.

@cavin-macwan
Created February 19, 2025 19:45
Show Gist options
  • Save cavin-macwan/6faa320e5a098504a3229acf39dfb5f0 to your computer and use it in GitHub Desktop.
Save cavin-macwan/6faa320e5a098504a3229acf39dfb5f0 to your computer and use it in GitHub Desktop.
Collapsing Toolbar with Smooth Scroll Animation in Jetpack Compose
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.unit.sp
@Composable
fun CollapsingToolbar() {
val scrollState = rememberScrollState()
val imageHeight = 250.dp
val toolbarHeight = 56.dp
val imageHeightPx = with(LocalDensity.current) { imageHeight.toPx() }
val toolbarHeightPx = with(LocalDensity.current) { toolbarHeight.toPx() }
Box(modifier = Modifier.fillMaxSize()) {
// Scrollable content
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState)
) {
// Spacer to push content below the image
Spacer(modifier = Modifier.height(imageHeight))
// Content
repeat(20) { index ->
Text(
text = "Item $index",
style = MaterialTheme.typography.bodySmall,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
Divider()
}
}
// Collapsing image
val collapseRange = (imageHeightPx - toolbarHeightPx).coerceAtLeast(0f)
val collapseFraction = (scrollState.value / collapseRange).coerceIn(0f, 1f)
val imageSize = lerp(imageHeight, toolbarHeight, collapseFraction)
val imageAlpha = 1f - (collapseFraction * 0.6f)
val imageOffsetY = -scrollState.value * 0.6f * (1 - collapseFraction)
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "Header",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(imageSize)
.graphicsLayer {
alpha = imageAlpha
translationY = imageOffsetY
}
)
// Title
val titleFontSize = lerp(24.sp, 18.sp, collapseFraction)
val titleOffsetX = lerp(16.dp, 56.dp, collapseFraction)
val titleTopPadding = lerp(imageHeight - 48.dp, 16.dp, collapseFraction)
Text(
text = "Collapsing Toolbar",
fontSize = titleFontSize,
color = Color.White,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(start = titleOffsetX, top = titleTopPadding)
.graphicsLayer {
shadowElevation = 8.dp.toPx() * (1 - collapseFraction)
}
)
val toolbarColor = MaterialTheme.colorScheme.primary.copy(
alpha = collapseFraction
)
Box(
modifier = Modifier
.height(toolbarHeight)
.fillMaxWidth()
.background(toolbarColor)
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment