Creating an Animated Border Composable in Jetpack Compose

Mohammad Bahadori
6 min readOct 16, 2023

--

Jetpack Compose has revolutionized Android app development by making it easier and more intuitive to create modern, interactive user interfaces. One creative way to enhance your UI is by adding animated borders. In this step-by-step guide, we’ll walk you through the process of creating and using an animated border Composable in Jetpack Compose.

Prerequisites

Before we dive into the world of animated borders, make sure you have the following prerequisites in place:

  • Basic understanding of Jetpack Compose
  • A Jetpack Compose project set up

Setting up Jetpack Compose

To create a Jetpack Compose project, you’ll need to set up the necessary dependencies and project structure. You can do this by following the official documentation for starting a new Jetpack Compose project.

Creating the animatedBorder Composable

In this step, we’ll explore the animatedBorder Composable, which is the heart of our animated border feature. It allows you to add dynamic and visually appealing borders to any Composable element in your Jetpack Compose UI.

@Composable
fun Modifier.animatedBorder(
borderColors: List<Color>,
backgroundColor: Color,
shape: Shape = RectangleShape,
borderWidth: Dp = 1.dp,
animationDurationInMillis: Int = 1000,
easing: Easing = LinearEasing
): Modifier {
// Code for creating the animated border goes here
// ...
}

Composable Parameters

  1. borderColors: This parameter accepts a list of Color objects that define the colors of the animated border. You can provide multiple colors to create a gradient effect.
  2. backgroundColor: This parameter sets the background color of the Composable element, allowing you to create a contrast between the border and the content.
  3. shape: By default, the border and content use a RectangleShape, making them rectangular. However, you can customize the shape as desired.
  4. borderWidth: This parameter determines the width of the animated border. You can specify the width in density-independent pixels (Dp).
  5. animationDurationInMillis: The duration of one full animation cycle in milliseconds. You can adjust this value to control the speed of the animation.
  6. easing: Defines the animation easing curve. The default is LinearEasing, but you can choose other easing functions to achieve different animation effects.

Brush Creation

The first step in creating the animated border is to define a brush that represents the border’s colors. We use the Brush.sweepGradient function to create a sweep gradient brush based on the specified borderColors.

// Create a sweep gradient brush for the border.
val brush = Brush.sweepGradient(borderColors)

This brush will be used for rendering the border’s colors.

Infinite Transition

The next crucial element is the use of rememberInfiniteTransition to create continuous animation. This means the animation will keep looping, creating a perpetual spinning effect.

// Create an infinite transition for continuous animation.
val infiniteTransition = rememberInfiniteTransition(label = "")

The infiniteTransition object will manage the animation for us.

Animating the Angle

The primary animation occurs on the angle of rotation. The angle rotates from 0 to 360 degrees, creating the spinning effect. We use the infiniteTransition.animateFloat function to animate the angle:

// Animate the angle from 0 to 360 degrees.
val angle by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = animationDurationInMillis, easing = easing),
repeatMode = RepeatMode.Restart
), label = ""
)

This animation involves the following parameters:

  • initialValue: The starting angle (0 degrees).
  • targetValue: The ending angle (360 degrees).
  • animationSpec: This specifies the animation's duration and easing curve. In this case, it's set to loop the animation with the given duration and easing function.
  • label: A label for the animation (can be empty).

Applying the Border and Background

Now that we have our animation, we apply it to the Composable using the drawWithContent function:

return this
.clip(shape) // Apply the specified shape to the border.
.padding(borderWidth)
.drawWithContent {
rotate(angle) {
drawCircle(
brush = brush,
radius = size.width,
blendMode = BlendMode.SrcIn,
)
}
drawContent()
}
.background(color = backgroundColor, shape = shape)

Here’s what each step does:

  • clip(shape): Applies the specified shape to the border. This means that the animated border will follow the shape you defined or use the default rectangular shape.
  • fillMaxWidth(): Ensures that the Composable fills the available width. This is particularly useful for components like buttons that span the entire width of the screen.
  • padding(borderWidth): Adds padding equal to the specified borderWidth. This ensures that the animated border fits within the bounds of the Composable.
  • drawWithContent: This function allows you to draw the animated border and its content. The rotate(angle) block rotates the drawing by the current angle, creating the spinning effect.
  • drawCircle: Inside the rotation block, we use the drawCircle function to draw the animated border with the previously defined brush. The radius is set to the width of the Composable, and we use BlendMode.SrcIn to apply the colors of the border to the content.Blend modes in Jetpack Compose are used to control how the colors of different graphical elements interact when they overlap. The BlendMode.SrcIn keeps the parts of the source (border) that overlap with the destination (content). In other words, it applies the border colors only to the area where they overlap with the content, and the areas outside the content remain transparent..
  • drawContent(): After drawing the border, this function draws the actual content of the Composable.
  • background: Finally, the background color is applied to the entire Composable, following the specified shape.

This completes the creation of the animatedBorder Composable. You can apply this Composable to various UI elements in your Jetpack Compose project to add stunning, animated borders.

In the next steps of this guide, we will demonstrate how to use this animatedBorder Composable in your project and provide a live preview of the animated border in action.

Here’s What You Achieved:

@Composable
fun Modifier.animatedBorder(
borderColors: List<Color>,
backgroundColor: Color,
shape: Shape = RectangleShape,
borderWidth: Dp = 1.dp,
animationDurationInMillis: Int = 1000,
easing: Easing = LinearEasing
): Modifier {
val brush = Brush.sweepGradient(borderColors)
val infiniteTransition = rememberInfiniteTransition(label = "animatedBorder")
val angle by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = animationDurationInMillis, easing = easing),
repeatMode = RepeatMode.Restart
), label = "angleAnimation"
)

return this
.clip(shape)
.padding(borderWidth)
.drawWithContent {
rotate(angle) {
drawCircle(
brush = brush,
radius = size.width,
blendMode = BlendMode.SrcIn,
)
}
drawContent()
}
.background(color = backgroundColor, shape = shape)
}

Usage Example

Now that we’ve created the animatedBorder Composable, it's time to use it in your Jetpack Compose project. Here's an example of how to apply it within a Composable function:

@Composable
fun AnimatedBorderDemo() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(200.dp)
.animatedBorder(
borderColors = listOf(Color.Red, Color.Green, Color Blue),
backgroundColor = Color.White,
shape = RoundedCornerShape(16.dp),
borderWidth = 4.dp
)
) {
// Content goes here
}
}
}

In this example, we’ve created a Column that takes up the entire screen's size and centers its content both vertically and horizontally. Inside the Column, there is a Box with a size of 200dp that uses the animatedBorder Composable. The parameters for animatedBorder are set as follows:

  • borderColors: A gradient of colors starting with Red, transitioning to Green, and ending with Blue.
  • backgroundColor: The background color of the Box, which is set to White.
  • shape: The RoundedCornerShape with a corner radius of 16dp.
  • borderWidth: The width of the animated border, which is 4dp.

The // Content goes here comment is a placeholder for the content you want to place inside the Box.

Conclusion

Incorporating animated borders in Jetpack Compose is a fantastic way to enhance the visual appeal of your user interface. You can use this technique to create various animations and apply them to different UI elements, providing a dynamic and engaging user experience.

So, go ahead and experiment with animated borders in your Jetpack Compose project. Play around with different color combinations, shapes, and durations to achieve the desired visual effect for your app.

Remember, the key to successful app development is continuous learning and experimentation, so stay curious and keep building amazing apps with Jetpack Compose!

Additional Resources

--

--