AndroidView & Jetpack Compose Tutorial

Hire us

Work with Exyte

Your name
E-mail
Website or company name (optional)
How can we help?
Thank you for your interest! A member of our team will be in touch as soon as possible.

AndroidView & Jetpack Compose Tutorial

How to make a shaped text background just like in Instagram

Sometimes in our app we can’t predict where text will be placed on the screen. For example - a user can create a text sticker and place it wherever they want with their photo as the background. This can potentially make the text unreadable, when placing a dark text on a dark background, or vice versa.

One of the possible solutions is always placing the text on a background. However, this background should not be jarring. Here is what we wanted to achieve:

Problem

An obvious first choice is to use the standard background property. However, this will be the result: As you can see, with the standard means, the background on the second line of text is displayed with the full width of the element, the edges are not rounded.

Making variable width

How can we make the background of each line be drawn to the maximum width of the text? Let’s measure each line and draw a rectangle behind the text:

Note that the getLineLeft/getLineRight method should be used instead of getLineStart/getLineEnd because the latter gives an offset in characters, not in pixels.

Once we have measured each line, we create a common Path for all text:

Next we will create a custom ColorDrawable, and draw the shape there:

And now we can easily use the background:

For EditText elements we calculate the background not only when the layout changes, but also after text updates.

Here is what it looks like:

We will continue using this timeless wisdom by a famous philosopher and poet, Anthony Kiedis, as a sample text.

Smoothing out the sharp corners

One issue is fixed, but we still need to smooth out the sharp corners. If we start solving this problem head-on, the obvious way would be to draw Bézier curves for all the corners we need to smooth out:

Places where we will use Bézier curves

In the example above, you would need to draw 6 rounded corners and connect them. To do this we can use:

Add a cubic Bézier from the last point, approaching the control points (x1,y1) and (x2,y2), and ending at (x3,y3).

Points on which Bézier curves are plotted

Add a line from the last point to the specified point (x,y)

This approach draws line by line, so it’s necessary to remember the prior parameters of the figure in order to calculate the latter ones. The implementation turned out to be rather difficult to understand, although it did work.

An alternative approach

How can we make this more comprehensive? Ideally the sharp corners would be smoothed out automatically. Turns out there is a specific property which does just this and encapsulates all of the math - the Paint class has a property pathEffect that can be set to CornerPathEffect, which will draw the smoothed corners.

As always, it pays off to read the documentation before implementing something complex, since it was most likely written by another developer before you. However, the cumbersome approach is useful for understanding what happens inside the library implementation, and can be tweaked if you desire a specific smoothing effect that native options don’t cover.

Final API

We describe a simple API for using the element - there’s a way to provide a background color, shadow parameters, padding values and the rounded corner radius.

Instead of using a solid backgroundColor, you can use a gradient by specifying:

What about Compose?

After implementing this element using Android View, we decided to compare it to a Jetpack Compose implementation. Would it be beneficial to use the newer framework?

Turns out, there is almost no difference. In Compose, AndroidPaint is used instead of Paint, and you can also call the function:

This function will draw on the old canvas and reuse as much of the old drawing logic as possible. There is only one state we need to take care of - TextLayoutResult. We will use its persisted value when defining the decorationBox, which is a composable lambda for adding decorations around the text field, such as icon, placeholder, helper messages or similar.

This is how you can use it in your code:

As with the Android View version, you can specify a gradient instead of a solid background color.

Conclusion

The implementations shown here can help you create a custom shape background for a text in your Android apps. It should be a good basis for a more customized version should you need one. The repo contains both Android View and Jetpack Compose implementations as standalone libraries. The interesting parts of the implementation were discussed in this short tutorial, which will be the first in a series of Android View and Jetpack Compose tutorials we will publish in our blog. We also post iOS tutorials and libraries written in SwiftUI.