Animating brush Text coloring in Compose 🖌️

Alejandra Stamato
Android Developers
5 min readAug 1, 2022

--

After adding a gradient to your text in Compose, now it’s time to animate it!

This post builds up on text styling using the Brush API and Compose animations! Make sure you’re familiar with these concepts. We covered all the integrations using the Brush API on this blog post here.

Candy cane shimmer effect

We want to implement a candy cane effect for the gradient in our text. The colors in the gradient should move from the top left to the bottom right corner of the text continuously, as per our design:

Candy cane animation with tileMode mirror

We can use linearGradient to create the gradient, which places the colors along given start and end coordinates.

Because of how linear gradient works, the coordinates define the area and angle at which the linear brush paints. For our example, if you define a region as described by the image below, the brush stroke is diagonally cropped, which is easy to see if the brush size is smaller than the drawing area:

Linear gradient with start in (0,0) and end in (n,m) coordinates

So an idea to implement this animation is to animate start and end coordinates in a small section of the drawing area and then repeat the sequence.

We can define an infinite transition, and use animateFloat to animate a value from 0f to at least twice your font size in pixels. Because we don’t know the drawing area size in this case, we are using font size as an anchor point, which directly relates to the area of the drawn text.

We can use this animated offset next, make the gradient move in a specific area of the canvas, and then repeat this sequence in the rest of the available space. This is how the first step would look like without repetition:

We can create the linear gradient brush, configure the start and end coordinates of the drawing area using the animated offset, and apply it to the text. We start in the (0,0) coordinate and then move up until our defined offset.

To repeat the sequence and create the effect of continuity, we can use tileMode mirror. So the code looks like this:

That’s it! You’ve achieved the candy cane effect that works for any text size and density of your device.

Candy cane animation with tileMode mirror

You can create other types of effects (for example a faster transition or a thicker gradient) by tweaking offset’s targetValue and the gradient’s offset parameters.

Back and forth shimmer effect

Next, we have a new animation, consisting of a gradient rocking back and forth.

In the previous example we used the currentFontSizePx to estimate how big the area of the animation would be, and using an estimate was good enough to achieve the effect we wanted. But sometimes, we may want to know the exact size occupied by the text or drawing area. To access this size (which is also the brush size), we can create a custom brush, as we did in the Custom brushes section, in the previous post.

We can start thinking how to draw the first pass of the gradient’s movement, with coordinates that are a function of an animated offset and the width and height of the drawing area.

Let’s define an infinite transition, animating a range from 0f to 1f. We can configure RepeatMode.Reverse, so the gradient can be painted moving in one direction and then in the opposite direction.

Because of the gradient pattern, we can create a linear gradient brush using the LinearGradientShader method. Same as before, from and to represent the coordinates where the brush will paint.

We can define widthOffset and heightOffset variables, that scale the width and height based on the animated offset.

And then we can use tileMode mirror to fill the drawing space with the inverse gradient allowing the effect of moving back and forth.

In between key frames without mirror effect
In between key frames with mirror effect

Finally, we set the new brush to Text. The code might look something like this:

We are using the remember function to allow recomposition whenever offset changes. This is necessary because, due to an internal optimization in AndroidTextPaint, if the brush or its direct parameters are not changing no recomposition will be scheduled. In this case, brush remains the same, and what changes is the LinearGradientShader’s parameters.

And you’re done, you’ve achieved the design above ✨.

Rocking animation with tileMode mirror

Recap

You’ve learned how to use the animation APIs with the different Brush APIs and some techniques to make your gradient colors come to life.

Give it a try in your app and let us know if you experience any issues by filing a bug on our issue tracker.

Happy Composing! 👋

This post was written with the collaboration of Halil Özercan on the Jetpack Compose Text team. Thanks to Rebecca Franks and Florina Muntenescu on the DevRel team for their thorough reviews.

--

--

Alejandra Stamato
Android Developers

Lead Android Developer | Ex Android DevRel @ Google | 🇦🇷 in London🏴󠁧󠁢󠁥󠁮󠁧󠁿