Brushing up on Compose Text coloring
Imagine your designer asks you to implement the sketch below:
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.
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.
There are 2 main components you’ll be working with:
Brush: It provides access to default brushes, most of them implementations of
ShaderBrush: When the default brushes are not enough, you can extend this class to implement your own custom brush.
To implement the design above, we define the list of gradient colors and use a
linearGradient brush on the
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.
Brush offers various predefined brush styles in its API. We already used
linearGradient, and you also have the following:
SolidColor brush paints with a single given color:
Check out the
Brush documentation for the complete API description.
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.
To access the brush size, you can create your own
Brush, by extending abstract class
ShaderBrush and overriding
The gradient pattern is repeated with a strategy given by
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.
mirrormirrors the colors at the edges of the pattern from last to first.
clampwill complete the drawing area by painting with the the color on the edge of the gradient:
decalis 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
tileModeis supported by using the
isSupportedmethod. If it isn’t, it will fallback to
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:
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 can be used together with all elements that receive styling components
For example, you can configure a
Brush as style for your
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:
Opacity with Brush
Compose version 1.3.0-alpha01 introduces an alpha optional parameter to
SpanStyle, which allows you to modify the opacity of the whole Text when using a color gradient to implement something like this:
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
SpanStyle , take a look at the
BrushDemo examples in AOSP.
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.