Creating an animated selector in Jetpack Compose

Laying out the selector options

  1. We define our composable accepting the arguments we described, a list of options, the selected option, a callback for when an option is clicked on, and an optional Modifier.
  2. For our implementation we require that there are at least 2 options, and that the selected option is one of the available options. You could make the selected option nullable to have a control with no selected option by default, but for my use case there will always be a selected option.
  3. Next we use the Layout composable to render the content. This takes a composable lambda that emits the content, and a MeasurePolicy that will measure and lay the content.
  4. We use a background with Surface color and rounded corners for our main composable.
  5. For the content, we will build a set of Boxes each containing a text composable with the text being the option, centered in the container.
  6. In the MeasurePolicy lambda, we receive the measurables (our content), and the constraints to apply to the composable. As we want our content to be of equal widths, we divide the total width by the number of options we have — this is how wide each item will be.
  7. Once we know how wide each element will be, we generate the Constraints to measure them with, using that calculated width as the fixed width, and the max height as our incoming height.
  8. Next we measure all the measuables using the constraints we just generated. Because we used Constraints.fixed, our Boxes will fill all the available space given to them. The result of this operation is a list of placeables.
  9. Now we can lay out the content, by calling the layout method.
  10. We iterable over our placeables and place them on a row. As we forced each one to be of exactly the same width, we just need to offset them by the item width.

Adding the selected item background

  1. We define an enum to represent our 2 object types.
  2. When we define the composables for the options, we specify a layoutId and set its value to indicate it’s an option.
  3. We add a Box for the background, setting its color to primary and, like on the options, we set the layoutId so that we can identify this object later.
  4. When we get our measurables, we filter the list and retrieve only those whose layout ID identifies them as options, and measure them.
  5. We do the same with the background — we know there is only 1, so we can use the first operator on the list
  6. We lay the background, presently on the left side of the container — before the options, so that it is behind them.

Animating the background

Defining the state

  1. We define a Stable interface for our state.
  2. The state exposes the currently selected item index, as a Float.
  3. We expose a method to tell the state that an item has been selected. This takes two arguments, a coroutineScope so that we can animate the index transition in a coroutine, and the index of the selected item.
  4. Our state implementation constructor takes 2 arguments, the list of options and the selected option.
  5. In our implementation we override the index, by taking a snapshot of the current value in the animation.
  6. Our index is internally represented as an Animatable object, which we initialize to the index of the currently selected item.
  7. Next we define an animation spec to describe how the background will animate. For the animation, we want the background to initially accelerate as it starts moving, and then easy off and come to a stop as it reaches its final value; FastOutSlowInEasing is what we need.
  8. Finally we implement the selectOption method, where we trigger the animation, animating towards the selected index.
  9. We also provide a utility method to instantiate and remember our state.

Animating the background

  1. We update the signature of our composable to accept the state, and we default it to our default implementation using the utility remember function. Accepting the state as an argument allows clients to provide their own implementation if they so choose.
  2. We use a LaunchedEffect to trigger the animation, keying off the options list and the currently selected option.
  3. In the body of the LaunchedEffect we update our state with the index of the selected, so that the animation can be kicked off.
  4. Finally, we update the position of the background to take into account the animated index.

Rounding the corners

  1. Our state now exposes 2 additional properties, the percentage for the start and end corners.
  2. In our concrete implementation we expose these 2 new properties as a snapshot on the animatable value.
  3. The start corner is an Animatable that we initialize to 50% if the item initially selected is the first one, and 15% otherwise.
  4. We do the same for the end corner, but this time we check if the selected item is the last one.
  5. Next we update the selectOption method to animate the start corner — similar to the initial value, if the selected option is the first one we animate to 50%, otherwise to 15%.
  6. And we do the same with the end corner, checking if the selected item is the last one.
  1. When we define our composable content we add a clip modifier to the background, specifying rounded corners with the percentage as determined but our 2 new state properties.

Animating the text color

  1. We update our state to expose a list of Colors, one for each label in the selector.
  2. Our state implementation now receives the 2 text colors in the constructor, one for the selected item and one for the unselected ones.
  3. We override the color list property and provide a snapshot of our animated values.
  4. Unlike the other options which are based on an Animatable, the colors are derived from the index, so we use a derivedStateOf builder for the color state.
  5. To calculate the color, we use the lerp method, which stands for Linear Interpolation — it interpolates a value between a start and a stop value, based on a fraction between 0 and 1.
  6. The fraction is derived from the index — if the index matches the index of the option we are calculating the color for, then the fraction is 1 and we use the selected color. Otherwise, we calculate how far off we are from that index, truncating at 1 at most — which will result in a fraction of 0 and return the unselected color.
  1. We get the list of colors from our state.
  2. We apply a color to the text label, based on its index.

--

--

--

Senior Android Developer

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Why LaTeXise your paper?

Git: Solve Letter Case Problem

New release of “Template” feature for NEORT

Loop Structures — The Method Of Repeating Routines In Statements

3 Weeklies Every Software Engineer/developer Should Subscribe To

5 Things I Should’ve Focused More On at the Start of My Programming Career

Question mark at the end of hallway

Mastering MUMPS: Basic Commands

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Francesc Vilarino Guell

Francesc Vilarino Guell

Senior Android Developer

More from Medium

Animate on a Path with Android Jetpack Compose UI

Jetpack Compose equivalent of Android LiveEvent

Easy Android ListView Pagination using Jetpack Compose

Responsive Auto Resizing Text With Jetpack Compose

Blue And Brown Abstract Painting