Welcome 👋
Are you bored with the default dialog appearance animation in Jetpack Compose? Then you’ve come to the right place.
In this article, we will craft a stunning 3D animation for dialogs in Jetpack Compose within 5 minutes, which will delight your users.
Fortunately, Jetpack Compose allows us to do this easily 🤗
AnimatedDialog Function
To make the dialog reusable and applicable to any scenario, let’s craft a function that will hold the animation and allow us to specify the content and properties of the dialog.
@Composable
fun AnimatedDialog(
onDismiss: () -> Unit,
inAnimDuration: Int = 720,
outAnimDuration: Int = 450,
properties: DialogProperties = DialogProperties(),
content: @Composable (triggerDismiss: () -> Unit) -> Unit,
)
This function takes several parameters. Let’s go through them:
✨ onDismiss ➜ A callback function that gets triggered when the dialog is dismissed.
✨ inAnimDuration ➜ The duration of the dialog’s entry animation.
✨ outAnimDuration ➜ The duration of the dialog’s exit animation.
✨ properties ➜ Configuration properties for the dialog.
✨ content ➜ A composable lambda that defines the content of the dialog. This lambda receives a function (triggerDismiss
) which can be used to dismiss the dialog programmatically with the exit animation.
Implementation
Alright, now we can proceed to the implementation of the animation.
Defining Variables
First thing, we need to define some variables:
// Coroutine scope to handle exit animation
val scope = rememberCoroutineScope()
// A state to manage the animations
var isDialogVisible by remember { mutableStateOf(false) }
// A common animation spec which will be used among different animations
val animationSpec = tween<Float>(
if (isDialogVisible) inAnimDuration else outAnimDuration
)
Animation States
Next, we need to define three animation states for alpha
, rotationX
, and scale
:
val dialogAlpha by animateFloatAsState(
targetValue = if (isDialogVisible) 1f else 0f,
animationSpec = animationSpec
)
val dialogRotationX by animateFloatAsState(
targetValue = if (isDialogVisible) 0f else -90f,
animationSpec = animationSpec
)
val dialogScale by animateFloatAsState(
targetValue = if (isDialogVisible) 1f else 0f,
animationSpec = animationSpec
)
Dismiss with Animation Lambda
We also need to define the lambda function that will dismiss the dialog with an exit animation, which we will pass for the content:
val dismissWithAnimation: () -> Unit = {
scope.launch {
// Trigger the exit animation
isDialogVisible = false
// Wait for completion
delay(outAnimDuration.toLong())
// Trigger dialog dismiss
onDismiss()
}
}
Triggering Entry Animation
We want to trigger the entry animation when the composable has been launched. For that, we can use LauchedEffect
with Unit
as the key:
LaunchedEffect(Unit) {
isDialogVisible = true
}
Crafting the Dialog
In the final part, we are ready to put it all together and craft the dialog with this captivating animation:
Dialog(
onDismissRequest = dismissWithAnimation,
properties = properties
) {
Box(
modifier = Modifier
// Apply alpha transition
.alpha(dialogAlpha)
// Apply scale transition
.scale(dialogScale)
// Apply rotation x transition
.graphicsLayer { rotationX = dialogRotationX },
content = {
content(dismissWithAnimation)
}
)
}
Congratulations🥳! We’ve successfully built it👏. For the complete code implementation, you can access it on GitHub Gist🧑💻. Now, let’s explore how we can put it to use.
Advertisement
Are you learning a foreign language and struggling with new vocabulary? Then, I strongly recommend you check out this words-learning app, which will make your journey easy and convenient!
Usage
Alright, now we can use this function to craft a common sample.
Declare a Variable
First, declare a variable to hold the dialog state:
var showDialog by remember { mutableStateOf(false) }
Button to Trigger the Dialog
Next, we need a button to trigger the dialog open:
Button(
onClick = { showDialog = true }
) {
Text("Open Dialog")
}
Display the Dialog
Finally, when showDialog
is set to true, we display the dialog:
if (showDialog) {
AnimatedDialog(
onDismiss = { showDialog = false }
) { triggerDismiss ->
Column(
modifier = Modifier
.fillMaxWidth()
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(24.dp)
)
.padding(16.dp)
) {
Text(
text = "👏 Clap Alert Dialog",
color = MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.surface),
style = MaterialTheme.typography.titleLarge
)
Spacer(Modifier.height(8.dp))
Text(
text = "Please show your appreciation by hitting the clap button.",
color = MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.surface),
style = MaterialTheme.typography.bodyLarge
)
Spacer(Modifier.height(16.dp))
Button(
onClick = triggerDismiss,
modifier = Modifier.align(Alignment.End)
) {
Text("Clap")
}
}
}
}
Check out the output 😍
There’s plenty of room for customization, but I hope this gives you a point of inspiration. Good luck finding your perfect setup! ✨
You might also like 👇
Thank you for reading this article! ❤️ I hope you’ve found it enjoyable and valuable. Feel free to show your appreciation by hitting the clap 👏 if you liked it and follow Kappdev for more exciting articles 😊
Happy coding!