A Playful Chips Selector

A short article on how to create a single or multi choice selector with playful animations

Introduction

In this short article we'll learn how to create a Chips selector, allowing users to select one or multiple options from a set. The animation below shows what the end result will be:

Creating the chip

The first thing we need for this component is the Chip composable, which shows a label with a rounded corners background. The background color animates when the chip is selected, and the border animates clockwise, starting at the 12 o'clock position.

Basic chip

Let's start by implementing a basic static chip that just displays the label and the background win rounded corners:

If we preview this composable, showing 2 chips side by side, one selected and one not, we get the following result

Adding the border

Next we want to draw the border. The border will be animated as we saw in the preview at the beginning of this article, so we can't use the border extension function on Modifier for this and we will instead have to draw the border manually. For this, we will have to define a Path, starting at the 12 o'clock position and going around the Chip clockwise. For this exercise we will simplify things a bit and work on the assumption that the chip is wider than taller, so that the corner radius is determined by the height of the chip. The steps we need to draw the path are detalied below:

Now that we understand how to build the path for the border, let's see the code:

These are the changes we've added:

This is the result:

Animating the border

Now that we have the border drawn, it's time to animate it. The way we are going to do this is by using the PathMeasure class. This class allows us to measure the length of a path, and get a segment of that path. With that, we can animate the path segment from 0 to the full length, using a compose animation.

We will start by animating just the path, but because we want to animate other properties (the text and background colors, and the text alpha), we will use updateTransition to coordinate the animations.

Let's see how we can animate the path of the chip based on its selected state:

Here are the changes to animate the border:

With these changes we get this result:

Animating the background

Next we will animate the background. This is straightforward, we have 2 colors for the background based on whether the chip is selected or not, so all we need to do is use a color animation to transition from one color to the other. Color animations are native supported in Jetpack Compose using the animateColor extension on the transition animation - let's see the necessary changes below:

We now get this result, with a nice smooth transition between the 2 background colors:

The text color animation follows the same principle as the background animation, so I'll skip it and move to the alpha animation of the text, as we want to deemphasize the text for the unselected chips.

For this we will create another float animation, similar to the one we used for the border, but in this case we want to animate from 1f (for a selected chip) to 0.6f (for an unselected chip). Let's see what changes we need for this:

With this our Chip is complete, the result is shown below:.

Now that we have a chip, we'll build on it to create a chip selector. 

Creating the chip selector

We want the chip selector to work in 2 modes, single selection and multi selection. As has been described in other articles, a good approach for this kind of composable is to create a state holder class that is responsible for the business logic of the composable, so we'll do the same here. We'll start by defining what our composable needs to be provided and what it will expose:

Clients of the composable will be able to observe changes to the select list by simply holding a reference to the state class. Let's start by defining the API for the state holder class:

Now that we have the API defined, we need to create an implementation. Let's see how we can do so:

There is something that I'd like to point out at this state. This implementation works on the assumption that chips are unique - if your scenario requires providing duplicated chips, then the implementation would need to change to keep track of the indices as well as the cihps, so that for any given chip we can discern which of the duplicates the user clicked on.

Now that we have the state in place, creating the chip selector is trivial, let's see the code:

And with this the implementation is complete. There are a few nieceties that we can add at this stage, like allowing callers to set colors and the border width; these improvements are available on the final gist here.