Crafting Custom 3D Dialog Animation in Jetpack Compose.

Kappdev
4 min readMay 30, 2024

--

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!

WordBook

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 😍

Slow Motion

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!

--

--

Kappdev

💡 Curious Explorer 🧭 Kotlin and Compose enthusiast 👨‍💻 Passionate about self-development and growth ❤️‍🔥 Push your boundaries 🚀