Android Developers
Published in

Android Developers

Brushing up on Compose Text coloring

Imagine your designer asks you to implement the sketch below:

Screen design with a main Text and a Button

Building this screen in Jetpack Compose should be straightforward, except for the Text’s gradient color.

Let’s explore some general strategies you could have used to implement a gradient in Compose before version 1.2.0.

A first approach could be to use Compose’s Canvas, drawing directly onto the native android.graphics.Canvas:

A more Compose-idiomatic approach would be to use the drawWithCache modifier on the text, together with a Brush:

Text with gradient painted using Compose modifiers

Here, the strategy is drawing a rectangle with gradient colors on top of the text and then blending it using SrcAtop to make sure that only text is visible and the rest of the rectangle is cut. This approach, however, draws over emojis (as you can see above), and inline content.

Both solutions require deeper knowledge of drawing APIs, Canvas and Paint. Starting with Compose 1.2.0 we have a better solution!

Brush API

Compose 1.2.0 adds Brush API to TextStyle and SpanStyle to provide a way to draw text with complex coloring, with gradient being only the beginning.

All usages of Brush in TextStyle are experimental, so make sure you add the ExperimentalTextApi annotation where necessary.

There are 2 main components you’ll be working with:

To implement the design above, we define the list of gradient colors and use a linearGradient brush on the TextStyle.

Brush.linearGradient

That’s it! It is really that easy. Notably, with this solution the brush won’t draw over emojis, as they are skipped by the underlying shader.

Default brushes

Brush offers various predefined brush styles in its API. We already used linearGradient, and you also have the following:

Additionally SolidColor brush paints with a single given color:

SolidColor(Cyan)

Check out the Brush documentation for the complete API description.

Custom brushes

There are instances where you might need to know exactly the brush’s size or drawing area and perform some calculations with it- like decreasing the size of your brush to achieve a particular tiling effect. Below, see how we are able to achieve this using custom brushes.

Repeating a color pattern

Imagine we want to achieve a certain color pattern to be repeated three times. An easy way to do this would be to reduce the brush size to a third of the drawing area and then repeat the sequence.

Design we want to implement with a repeated pattern

To access the brush size, you can create your own Brush, by extending abstract class ShaderBrush and overriding createShader() method.

The gradient pattern is repeated with a strategy given by tileMode parameter.

The tileMode parameter determines the behavior for how the shader fills a region outside its bounds. Because of how the repetition is calculated, it’s clearer to notice the effect when:

  • Your brush is forced to be smaller than the text layout (like in this case).
  • Your drawing coordinates are smaller than the available drawing area.
  • Using a radial gradient brush.

You can use the following tile modes:

  • repeated (just used above) restarts the colors at the edges, repeating the sequence.
  • mirror mirrors the colors at the edges of the pattern from last to first.
  • clamp will complete the drawing area by painting with the the color on the edge of the gradient:
  • decal is supported starting Android S (API 31) and above, it draws the pattern given by the brush size and completes the rest of the drawing area with black. You can check if this tileMode is supported by using the isSupported method. If it isn’t, it will fallback to tileMode clamp.

Image pattern as text coloring

Let’s say that we need to use the colors of an image as text color. For example, using the Jetpack Compose logo, we would want this result:

Text with colors defined by a Bitmap

To implement this, we use a method to create a ShaderBrush, pass in a native BitmapShader and set the Bitmap we want to use.

We’re using the remember function to save the ShaderBrush across recompositions, because creating a shader can be costly and every call to ShaderBrush would cause a new allocation for BitmapShader as well.

Brush integrations

Brush can be used together with all elements that receive styling components TextStyle and AnnotatedString.

For example, you can configure a Brush as style for your TextField:

TextField value styled with Brush

Make sure to use the remember function to persist the brush across recompositions, when the TextField state changes on each new typed character.

Additionally, some performance optimizations are done under the hood. For example, converting a brush to a shader can be an expensive operation but AndroidTextPaint optimizes this process for brushes that don’t change between compositions (like in this case).

To add a gradient to only selected parts of your text or paragraph, you can build an AnnotatedString and set a Brush style to only specific spans of your text, like this:

Text with gradient built with brush in SpanStyle

Opacity with Brush

Compose version 1.3.0-alpha01 introduces an alpha optional parameter to TextStyle / SpanStyle, which allows you to modify the opacity of the whole Text when using a color gradient to implement something like this:

“Text in” has 0.5f alpha

To achieve this, we’ll use the same brush for both parts of a text and we will change the alpha of the text in its corresponding span.

For more examples of Brush and TextStyle / SpanStyle , take a look at the BrushDemo examples in AOSP.

And for more inspiration of what can be achieved with brush in Compose, check out this demo by Halil Özercan (you can find the code here).

Recap

We hope that by having new exciting Compose-idiomatic APIs that seamlessly integrate with APIs you’re already familiar with, will help you create beautiful visuals for your most creative use cases.

If you find any bugs while using Brush API, please let us know by filing a bug on our issue tracker. To learn more about Canvas in Compose in general, you can check out our documentation on Graphics in Compose.

We can’t wait to see what you build with Brush. Tag me on Twitter @astamatok so I can see your beautiful creations!

Last but not least, stay tuned for the second part of this blog post! There, we’ll learn how to animate colors painted with brush 🖌️

Happy Composing! 👋

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

--

--

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
Alejandra Stamato

Alejandra Stamato

Android Developer Relations Engineer 🥑 @ Google | 🇦🇷 in London🏴󠁧󠁢󠁥󠁮󠁧󠁿 — ex 🇮🇪