Blog Infos
Author
Published
Topics
,
Published
Creating a custom button with Jetpack Compose, including custom animations.

In this part of our series on introducing Jetpack Compose into an existing project, we will create a custom button we often use around our app at Lemonade

First, let’s have a look again at the screen we are building.

 

 

We already covered how to build the , in this article we will focus on the pink button.

The Lemonade pink button

The lemonade pink button is a UI component we use in several flows, and aside from having specific characteristics like height, corner radius, font sizes, and colors, it also has 2 specific animations to transition from enable state to disable state.

Color change animation – our button changes color from pink to grey based on its enablement state, and it animates that color change.

Translation animation – our button moves up 10 pixels when is enabled and down 10 pixels when disabled.

Let’s get started

We first start, as always with writing a composable function.

What do we have here?

Let’s start with looking at the function signature, we have:

@Composable
private fun PinkButton(
  text: String,
  enabled: Boolean,
  onClick: () -> Unit
) { ... }

 

So our function takes the minimum amount of information following the , the text for the button, the onClicklambda, and the enable state, that is declared from its parent.

Next, we declare the Button composable which takes only the state and invokes the onClick lambda when the button is clicked.

@Composable
private fun PinkButton(...) {
   Button(
     enabled: enabled,
     onClick: { onClick() }
   )
}

 

Inside the button, we declare the Text composable, which takes the text from the PinkButton and defines some font-related properties.

 

@Composable
private fun PinkButton(...) {
   Button(...) {
     Text {
         text = text,
         fontFamily = latoFamily // We discussed it the last article
         fontWeight = FontWeight.Bold,
         fontSize = 12.sp,
         color = colorResource(id = R.color.my_color_resource)
     }
   }
}
A word about context

One thing that is important to notice here is how we can access our resource folder easily without the need for Context using colorResource(id = …), Compose is not bound to lifecycles unless explicitly declared, therefor it does not need to use the Context interface to interact with system resources.
As a side note, if we need to access Context in a composable, we can do so using LocalContext.current which will provide the context of the view that called setContent.

And now we have the basic function for the button, but it still doesn’t look right doesn’t it?

Using Modifier to modify a composable

Now that we have the basic frame for the button we want to add some modifications to it, to shape and style it to look like our desired pink button.

So in the Button function call we will add a new parameter called Modifier.

A Modifier is an interface that can be implemented using the builder pattern to chain together parameters in order to assign visual and behavioral properties.

We add the modifier properties to the Button function like this.

@Composable
PinkButton(...) {
   Button(
      enabled = enabled,
      onclick = { onClick() }
      modifier = Modifier.padding(...).fillMaxWidth().height(58.dp)
   ) {
     Text(...)
   }
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

With the advent of Android 15, edge-to-edge design has become the default configuration. Consequently, applications must be capable of accommodating window insets, including the system status bar and navigation bar, as well as supporting drawing…
Watch Video

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android developer
Seven Principles Mobility GmbH

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android develop ...
Seven Principles Mob ...

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android developer
Seven Principles Mobility ...

Jobs

Notice how we chained padding, fillMaxWidth, and height to implement the Modifier.

We can chain as many modifiers as we want and by doing so we can style our composable.
So for our composable we will add the following modifiers

@Composable
PinkButton(...) {
   Button(
      enabled = enabled,
      onclick = { onClick() }
      modifier = Modifier
          .padding(bottom = 10.dp, start..., top..., end...)
          .fillMaxWidth()
          .height(58.dp)
          .clip(RoundedCornerShape(10.dp)
   ) {
     Text(...)
   }
}

Let’s take a look at the modifiers we used here:

  • padding – apply additional space along each edge of the content in dp: start, top, end, and bottom.
  • fillMaxWidth – equivilient to match_parentin the view system.
  • height – declare the preferred height of the content to be exactly.
  • clip – clip the view using a specified shape, in this case we use RoundedCornerShape that takes a dp value of the corner radius.

Somthing that is important to remember is the modifiers are applied in the order they are written, so the padding will be implemented before the height and width in our case.

Other than visual properties we can also add behavioral properties like

After we added the modifications, the button looks better but it is still missing its colors and custom animations.

Adding custom animations

Our pink button has two types of animation when its state is modified from enabled to disabled.

  • Translation Y animation — the button will move 10 px on the Y axis up or down according to its enablement state.
  • Color change animation — the button will animate color change from pink to grey according to its enablement state.

To achieve simple animations between values we can use a an API called animate*AsState functions.

Animate*AsState

In Jetpack Compose we have a an API that allows us to animate to a target value, the * denotes that you can replace a data type.
We can animate values of Float, color, dp etc’.
Once the function is called, it will animate to target value across recomposition until it hits its target value.

Animating the movement on the Y axis

In our function we will add one more modifier to the Button composable.

Button(
    enabled = enabled,
    modifier = Modifier.graphicsLayer(
          translationY = animateFloatAsState(
                              if (enabled) 0f else 10f).value
                         )
           )

As we can see, we are using the animateFloatAsState API here to animte between two float values, the float value changes according to the enableparameter from 0f to 10f.
So the value given to the animateFloatAsState function will change when the enable state changes, and will trigger an animation on the Y axis, since the value is assigned to the tranbslationY modifier.

Animating color change

To add an animation for color change we will define two variables inside the PinkButton function called animatedColor and buttonColors, we can do it like this:

@composble
fun PinkButton(...) {
   val animatedColor
   val buttonColors
   Button(color: ButtonColors = buttonColors) {
      Text(...)
   }
}
What have we here?

When be want to set colors for a button in Jetpack Compose we will assign it a ButtonColors implemantaion that will hold values for the buttons backgroundColor and contentColor.

The backgroundColor for us should be grey if the button is disabled, or pink if the button is enabled, we can assign it like this:

val buttonColors = ButtonDefaults.buttonColors(
    backgroundColor = if (enabled) Color.Pink else Color.Grey
)

But if we do that we wont get an animation change between the two colors, to get the animation change we will use the other variable we declared — animatedColor.

val animatedColor = animateColorAsState(
                        if (enabled) Color.Pink else Color.Grey
                    )

We will assign animatedColor value to the backgroundColor.

val buttonColors = ButtonDefaults.buttonColors(
    backgroundColor = animatedColor.value
)

And now we get the animation thanks to animateColorAsState which works exactly like animateFloatAsState by animating its given value to the target value when the enable state changes.

So the full code for our Pink button is now this:

@Composable
fun PinkButton(
text: String,
enabled: Boolean,
onClick: () -> Unit
) {
val animatedColor = animateColorAsState(
if (enabled) Color.Pink else Color.Grey
)
val buttonColors = ButtonDefaults.buttonColors(
backgroundColor = animatedColor.value
)
Button(
enabled = enabled,
colors = buttonColors,
onClick = { onClick() },
shape = RoundedCornerShape(10.dp),
modifier = Modifier
.padding(start = 15.dp, end = 15.dp, bottom = 15.dp)
.fillMaxWidth()
.height(58.dp)
.graphicsLayer(
translationY = animateFloatAsState(if (enabled) 0f else 10f).value)
) {
Text(...)
}
}
view raw PinkButton.kt hosted with ❤ by GitHub

And now our button looks and acts the way we wanted it to! 😍

 

 

And that’s it for now

In this article we built a custom button we two types of animations, we built it as a composable function, which means we can call it wherever we want.

We went over new terms like Modifier, and the animate*AsState APIs, and learned how we can leverage them to style and animate our composable views.

If you liked the article don’t forget to clap and subscribe, but only if you think I deserve it!.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
Menu