Learning Android Development

Unlocking the Magic of Dynamic Composable Animations in Jetpack Compose

Elevate Your UI with Animated Visibility and Launched Effect in Compose

Nirbhay Pherwani
Mobile App Development Publication

--

About Article (Animation GIF sourced from Simon’s Web Blog)

Have you ever been captivated by the smooth, eye-catching animations in modern apps? From impressive entrance effects to graceful fades, animations can truly elevate the user experience. With Jetpack Compose, Google’s declarative UI toolkit for Android, creating these stunning animations has never been easier.

In this article, we’ll explore how to create two popular animation types — Scale Animation and Fade Animation — using Compose. We’ll make it simpler to add some values present by default. Finally, we’ll extend our knowledge to include SlideIn and SlideOut animations for an all-encompassing animated experience.

Scale Animation — Magnify / Minify Your Elements

Scaling elements is an excellent way to draw attention to specific parts of your UI. Let’s dive into the ScaleAnimation function.

@Composable
fun ScaleAnimation(
visible: Boolean,
startScale: Float = 0.8f,
endScale: Float = 1.0f,
animationDuration: Int = 300,
content: @Composable () -> Unit,
) {
AnimatedVisibility(
visible = visible,
enter = scaleIn(
initialScale = startScale,
animationSpec = tween(durationMillis = animationDuration)
),
exit = scaleOut(
targetScale = endScale,
animationSpec = tween(durationMillis = animationDuration)
)
) {
content()
}
}

Usage and Examples

By default, ScaleAnimation will smoothly scale the content from startScale to endScale within animationDuration milliseconds. However, you can customize these values to create unique animations. Example —

// Scale down the Button when visible
ScaleAnimation(visible = buttonVisibility, startScale = 1.0f, endScale = 0.8f, animationDuration = 500) {
Button()
}

In this example, when buttonVisibility is true, the Button will elegantly scale down from its original size to 80% of its size within 500 milliseconds.

Fade Animation — Subtle Elegance

Fade animations gracefully blend elements in and out, providing a more seamless user experience. Let’s uncover the FadeAnimation function.

@Composable
fun FadeAnimation(
visible: Boolean,
initialAlpha: Float = 0.0f,
targetAlpha: Float = 1.0f,
animationDuration: Int = 300,
content: @Composable () -> Unit
) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(
initialAlpha = initialAlpha,
animationSpec = tween(durationMillis = animationDuration)
),
exit = fadeOut(
targetAlpha = targetAlpha
)
) {
content()
}
}

Usage and Examples

By default, FadeAnimation will smoothly fade the content from initialAlpha to targetAlpha within animationDuration milliseconds. Let's explore an example —

// Fade in an image when it becomes visible
FadeAnimation(visible = showImage, initialAlpha = 0.2f, targetAlpha = 1.0f, animationDuration = 800) {
Image(
painter = painterResource(id = R.drawable.image),
contentDescription = "My Image",
modifier = Modifier.fillMaxWidth()
)
}

Here, when showImage becomes true, the image will elegantly fade in from 20% opacity to 100% opacity over 800 milliseconds.

Animated Visibility?

Both ScaleAnimation and FadeAnimation leverage the AnimatedVisibility composable from Jetpack Compose. This composable simplifies the process of animating UI elements based on their visibility.

To achieve this, we use the enter and exit parameters of AnimatedVisibility, which take animation specifications such as scaleIn, scaleOut, fadeIn, fadeOut, etc.

Slide-In and Slide-Out Animations

To complete our Composable animation toolkit, let’s explore SlideIn and SlideOut animations. To achieve this, we’ll use the offset modifier provided by Compose —

@Composable
fun SlideInOutAnimation(
visible: Boolean,
slideDirection: SlideDirection = SlideDirection.Right,
animationDuration: Int = 300,
content: @Composable () -> Unit
) {
val slideOffsetX = with(LocalDensity.current) {
when (slideDirection) {
SlideDirection.Right -> fullWidth.toPx()
SlideDirection.Left -> -fullWidth.toPx()
}
}

AnimatedVisibility(
visible = visible,
enter = slideInHorizontally(
initialOffsetX = { slideOffsetX },
animationSpec = tween(durationMillis = animationDuration)
),
exit = slideOutHorizontally(
targetOffsetX = { slideOffsetX },
animationSpec = tween(durationMillis = animationDuration)
)
) {
content()
}
}

enum class SlideDirection {
Right, Left
}

With this SlideInOutAnimation function, we introduce a parameter slideDirection of type SlideDirection, which can take either SlideDirection.Right or SlideDirection.Left. The function calculates the appropriate offset based on the chosen direction and performs the corresponding SlideIn or SlideOut animation for the content to be rendered.

Usage and Examples

Now, let’s explore how to use the SlideInOutAnimation function —

// SlideIn a card from the right when visible
SlideInOutAnimation(visible = showCard, slideDirection = SlideDirection.Right, animationDuration = 600) {
Card(
modifier = Modifier.fillMaxWidth().padding(16.dp),
elevation = 4.dp
) {
Text("SlideIn Card Content", modifier = Modifier.padding(16.dp))
}
}

// SlideOut a card to the left when visible
SlideInOutAnimation(visible = showCard, slideDirection = SlideDirection.Left, animationDuration = 600) {
Card(
modifier = Modifier.fillMaxWidth().padding(16.dp),
elevation = 4.dp
) {
Text("SlideOut Card Content", modifier = Modifier.padding(16.dp))
}
}

In these examples, we use the SlideInOutAnimation function for both SlideIn and SlideOut animations. By specifying the desired SlideDirection, the card will slide in or out accordingly.

Combining Animated Visibility and Launched Effect — A Dynamic Example

var itemVisible by remember { mutableStateOf(false) }

LaunchedEffect(
key1 = itemKey,
block = {
// Animate the item when it changes or first appears
itemVisible = false
delay(200) // Delay for visibility change to take effect
itemVisible = true
}
) {
FadeAnimation(
visible = itemVisible,
content = {
// Your item content here
},
animationDuration = 500
)
}

By leveraging AnimatedVisibility and combining it with LaunchedEffect, you can make your animations dynamic and interactive. For example, the item can repeatedly fade in and out if we keep toggling the itemVisible state variable in the code.

Congrats, you can now go ahead and breathe life into your UI with engaging animations! Experiment with these animations and elevate your app’s user experience to the next level.

Remember, in the world of user experience, every pixel matters!

I am always open to collaborating with fellow developers. Feel free to connect with me / follow me on LinkedIn.

Happy Animating!

--

--