I would recommend using AnimatedContent
for this. It enables you to specify virtually any kind of sliding animation and wrap composable functions into it. For example:
setContent {
var isClicked by remember { mutableStateOf(false) }
AnimatedContent(
targetState = isClicked,
transitionSpec = {
val durationMillis = 1000
if (targetState != initialState && targetState) {
(slideInVertically(animationSpec = tween(durationMillis)) { height -> height })
.with(slideOutVertically(animationSpec = tween(durationMillis)) { height -> -height })
} else {
slideInVertically(animationSpec = tween(durationMillis)) { height -> -height } with
slideOutVertically(animationSpec = tween(durationMillis)) { height -> height }
}.using(
SizeTransform(clip = false)
)
}
) { buttonClicked ->
if (buttonClicked) {
Column(
Modifier
.fillMaxSize()
) {
Text(
"Some content",
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.padding(top = 100.dp),
fontSize = 26.sp
)
}
} else {
Row(Modifier.fillMaxSize()) {
Column(
Modifier
.fillMaxWidth()
.align(Alignment.CenterVertically)
) {
Button(
onClick = { isClicked = true },
modifier = Modifier
.align(Alignment.CenterHorizontally)
) {
Text("Click me!")
}
}
}
}
}
}
Click here to see this UI as a GIF
Another way to achieve this is to use AnimatedVisibility
. For more information, see the docs:
https://developer.android.com/jetpack/compose/animation/composables-modifiers#animatedcontent
https://developer.android.com/jetpack/compose/animation/composables-modifiers#animatedvisibility
If you want to keep both parts of the UI on the screen, you can also leverage an in-built class Animatable
like this:
setContent {
with(LocalDensity.current) {
val screenHeight = LocalConfiguration.current.screenHeightDp.dp.toPx()
var isClicked by remember { mutableStateOf(false) }
val topBarHeight = remember {
androidx.compose.animation.core.Animatable(screenHeight)
}
LaunchedEffect(isClicked) {
if (isClicked) {
topBarHeight.animateTo(
targetValue = 300f,
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing)
)
}
}
Column(
Modifier
.fillMaxSize()
.background(Color.LightGray),
verticalArrangement = Arrangement.Bottom
) {
Column(
modifier = Modifier
.background(
Brush.horizontalGradient(
listOf(Color.Magenta, Color.Blue)
)
)
.fillMaxWidth()
.height(topBarHeight.value.toDp()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = { isClicked = true },
modifier = Modifier,
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.DarkGray,
)
) {
Text("Click me!", color = Color.White)
}
}
Column(
Modifier
.fillMaxWidth()
.height((screenHeight - topBarHeight.value).toDp())
) {
Text(
"Some content",
modifier = Modifier
.fillMaxSize()
.background(Color.White),
fontSize = 26.sp
)
}
}
}
}
What this code effectively does is taking the height of the screen and animating that value until it reaches a certain value that you choose, I've chosen 300 pixels. Its only disadvantage is that you cannot animate an integer (and, consequently, a Dp). Mind that the animation should be launched in a separate coroutine with LaunchedEffect
.
Again, check the GIF for the result:
Animation with top bar