The Art of Small Animations in Android with Jetpack Compose

Small details that make a difference for Users

Rodrigo Dominguez
6 min readJun 6, 2023

During the past few years, I have immersed myself in exploring and learning various animations in Android. Every day, I delve into creating and discovering new ways to bring interfaces to life in our applications.

In this article, I want to share with you some of the small animations I have come across and that we can integrate into our Android applications. These animations, although subtle, have the power to create a visually appealing and enjoyable experience for our users.

Join me on this journey as we explore how to add that special touch to our applications through the use of small animated details. We will discover how these animations can make a difference and provide an exceptional experience for those interacting with our Android apps.

Animation #1

Highlighting Relevant Information: Infinite Elevation in Cards for a Visually Striking Experience!

In this section, we will explore a fascinating animation designed to highlight cards that contain relevant information for our users. Through an infinite animation, we will bring these cards to life by playing with elevation, creating a depth effect that will capture attention and emphasize the importance of their content.

Here we have the necessary code to generate an infinite animation that affects the elevation attribute of our Cards.

val value by rememberInfiniteTransition().animateFloat(
initialValue = 1.dp.value,
targetValue = 16.dp.value,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
),
repeatMode = RepeatMode.Reverse
)
)

We assign it to our Card in the elevationfield:

Card(
modifier = .. ,
elevation = value.dp,
shape = ..
) {
...
}

And this is how our animation looks like:

Animation # 2

Highlighting Unread Alerts: Infinite Rotation of Notification Icon to Grab Users’ Attention!

In this section, I will show you how to implement a simple yet effective animation in your Android application. By using infinite rotation of the notification icon, we will capture users’ attention and emphasize the presence of unread alerts.

Here is the necessary code to generate an infinite animation that affects the rotationZ attribute of our Icon:

val value by rememberInfiniteTransition().animateFloat(
initialValue = 25f,
targetValue = -25f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 600,
easing = LinearEasing
),
repeatMode = RepeatMode.Reverse
)
)

To achieve the infinite rotation of the icon, we will assign the value to the rotationZ field of our Icon object. Additionally, we will use an important trick: through the .graphicsLayer method, we will set the pivot point of our rotation to be at the center of our icon (pivotFractionX = 0.5f) and at the top (pivotFractionY = 0.0f). This will create a visually appealing effect, similar to the ringing of a bell, that will capture users’ attention.

Icon(
imageVector = Icons.Outlined.Notifications,
contentDescription = ...,
modifier = Modifier
.graphicsLayer(
transformOrigin = TransformOrigin(
pivotFractionX = 0.5f,
pivotFractionY = 0.0f,
),
rotationZ = value
)
)

Result of our animation:

Animation #3

Highlighting Sections with Badges: Scaling a Composable to Grab Attention in Your Application!

In this section, we will explore an exciting technique to highlight important sections in your application using badges. We will utilize the scalability of a Composable to achieve a visually striking effect that will draw users’ attention to the desired section.

Here is the necessary code to generate an infinite animation that affects the scale attribute of our Composable:

val value by rememberInfiniteTransition().animateFloat(
initialValue = 0.5f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 600,
easing = LinearEasing
),
repeatMode = RepeatMode.Reverse
)
)

And we assign it to our Box in the scaleX and scaleY fields:

Box(modifier = Modifier
.graphicsLayer {
scaleX = value
scaleY = value
}
.size(25.dp)
.clip(CircleShape)
.background(Color.Red)
)

And this is how our animation looks like:

Animation #4

Highlighting Story Uploads: A Simple Example of Achieving an Animated Border Similar to Instagram Stories!

In this section, I will show you a simple yet effective example to highlight story uploads in your application, inspired by the popular Stories feature in Instagram. By implementing an animated border, we will capture users’ attention and visually communicate that a new story has been added.

Here is the necessary code to generate an infinite animation that affects the .drawBehind{ } attribute of our Composable:

val value by rememberInfiniteTransition().animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
)

And we assign it to our Box in the drawBehind method.

Box(modifier = Modifier
.drawBehind {
rotate(value) {
drawCircle(
gradientBrush, style = Stroke(width = 12.dp.value)
)
}
}
.size(125.dp)
)

And here I provide you with the gradientBrush:

val colors = listOf(
Color.fromHex("#405DE6"),
Color.fromHex("#C13584"),
Color.fromHex("#FD1D1D"),
Color.fromHex("#FFDC80")
)
var gradientBrush by remember {
mutableStateOf(
Brush.horizontalGradient(
colors = colors,
startX = -10.0f,
endX = 400.0f,
tileMode = TileMode.Repeated
)
)
}

Result of our animation:

Animation #5

Adding Drag Effect to Your Composables: Bring Your Application to Life with Gesture Interactions!

In this section, we will explore how to add the drag gesture to your composables, allowing them to return to their original position after releasing the drag. This technique is particularly useful for playing with components that contain hidden information, unlocking additional functionalities in your application.

Here is the necessary code to track and calculate the position of our users’ gesture:

val coroutineScope = rememberCoroutineScope()
val offsetX = remember { Animatable(0f) }
val offsetY = remember { Animatable(0f) }

We assign it to our Composable using the Modifier:

.offset {
IntOffset(
offsetX.value.toInt(),
offsetY.value.toInt()
)
}

We will listen to gestures on our Composable using .pointerInput and the onDrag lambda, changing the values of our offsetY and offsetX.

.pointerInput(Unit) {
detectDragGestures(
onDragEnd = {
// ...
},
onDrag = { change, dragAmount ->
change.consume()
coroutineScope.launch {
offsetY.snapTo(offsetY.value + dragAmount.y)
}
coroutineScope.launch {
offsetX.snapTo(offsetX.value + dragAmount.x)
}
}
)
}

And when the gesture ends, we will reset the position of our Composable as follows:

.pointerInput(Unit) {
detectDragGestures(
onDragEnd = {
coroutineScope.launch {
offsetY.animateTo(
targetValue = 0f,
animationSpec = tween(
durationMillis = 1000,
delayMillis = 0
)
)
}
coroutineScope.launch {
offsetX.animateTo(
targetValue = 0f,
animationSpec = tween(
durationMillis = 1000,
delayMillis = 0
)
)
}
},
onDrag = {
// ...
}
)
}

Result of our animation:

In summary, in this article, we have explored five simple yet powerful animations that can bring life and enhance the experience of Android applications. From infinite elevation in Cards to drag effects in Composables, each animation has the potential to captivate users and make your application stand out.

I hope you enjoyed discovering these techniques and feel inspired to implement them in your own projects. To access the complete code examples, I invite you to visit the following link on Gist: [Code with sample].

Remember that these animations are just the starting point, and you can experiment and customize them according to your needs. Don’t hesitate to unleash your creativity and explore further possibilities to enrich your Android applications.

Thank you for joining us on this animated journey, and we look forward to seeing you soon in future articles with more tips and tricks to enhance your Android app development skills!

--

--

Rodrigo Dominguez

Android Tech Lead @ Uala | Previously MercadoLibre, Despegar, Kavak.com