Before Compose 1.3.0, there was no drawText()
. We couldn’t draw text directly on the Jetpack Compose canvas, we had to use android native canvas canvas.nativeCanvas.drawTextto
draw text.
Recent release of Jetpack Compose 1.3.0 introduces many new APIs, and Text on Canvas is one of them.
In this article, we’ll explore the new DrawScope.drawText()
API. Note that this API is still in the experimental state and it is likely to change in the future.
Here’s the full source code if you need it.
Before we dive deep into the new API let’s see the old approach to drawing the text.
Here’s the code snippet
@Composable
fun NativeDrawText() {
val paint = Paint().asFrameworkPaint().apply {
// paint configuration
}
Canvas(modifier = Modifier
.fillMaxWidth()
.height(100.dp), onDraw = {
drawRect(color = Color.Black)
drawIntoCanvas {
it.nativeCanvas.drawText("Text on Canvas!", 20f, 200f, paint)
}
})
}
And the result
Now it’s time to explore DrawScope.drawText
drawText
function has 4 overloads, Let’s see them one by one with examples.
The First two overload takes TextMeasure
in an argument, while the other two variant takes TextLayoutResult
with other configuration parameters.
fun DrawScope.drawText(
textMeasurer: TextMeasurer,
text: AnnotatedString,
// other configuration...
)
TextMeasure is a new Experimental API. TextMeasure is responsible for measuring text layout. To learn more about TextMeasure, check out the official API doc.
With this overload function, we can draw styled text on canvas. With rememberTextMeasurer()
we can create an instance of TextMeasurer
rememberTextMeasurer()
takes cacheSize as parameters. cacheSize
defines the capacity of the internal cache inside TextMeasurer, which means the number of unique text layout inputs that are measured.
Let’s see the example now
@OptIn(ExperimentalTextApi::class)
@Composable
fun ExampleTextAnnotatedString() {
val textMeasure = rememberTextMeasurer()
val text = buildAnnotatedString {
withStyle(
style = SpanStyle(
color = Color.White,
fontSize = 22.sp,
fontStyle = FontStyle.Italic
)
) {
append("Hello,")
}
withStyle(
style = SpanStyle(
brush = Brush.horizontalGradient(colors = RainbowColors),
fontSize = 28.sp,
fontWeight = FontWeight.Bold
)
) {
append("\nText on Canvas️")
}
}
Canvas(modifier = Modifier
.fillMaxWidth()
.height(100.dp), onDraw = {
drawRect(color = Color.Black)
drawText(
textMeasurer = textMeasure,
text = text,
topLeft = Offset(10.dp.toPx(), 10.dp.toPx())
)
})
}
Here we have created textMeasure
instance by rememberTextMeasurer()
and annotated string with style also set up the topLeft
point. Also to style our text we’ve used Brush API to apply gradient coloring.
Nice colorful text on canvas 🤩.
Now let’s see the second overload function
This draw function supports only one text style and async font loading.
@ExperimentalTextApi
fun DrawScope.drawText(
textMeasurer: TextMeasurer,
text: String,
// Other configuration
)
The signature is the same as the previous overload function, except for the text
parameters which take the string instead of Annotated string.
@Composable
fun ExampleTextString() {
val textMeasure = rememberTextMeasurer()
Canvas(modifier = Modifier
.fillMaxWidth()
.height(100.dp), onDraw = {
drawRect(color = Color.Black)
drawText(
textMeasurer = textMeasure, text = "Text on Canvas!",
style = TextStyle(
fontSize = 35.sp,
brush = Brush.linearGradient(
colors = RainbowColors
)
),
topLeft = Offset(20.dp.toPx(), 20.dp.toPx())
)
})
}
And the output
Now let’s see the remaining two overloads which draw an existing text layout on canvas
TextLayoutResult can be generated by textMeasurer.measure()
@ExperimentalTextApi
fun DrawScope.drawText(
textLayoutResult: TextLayoutResult,
color: Color = Color.Unspecified,
topLeft: Offset = Offset.Zero,
alpha: Float = Float.NaN,
shadow: Shadow? = null,
textDecoration: TextDecoration? = null
){}
@ExperimentalTextApi
fun DrawScope.drawText(
textLayoutResult: TextLayoutResult,
brush: Brush,
topLeft: Offset = Offset.Zero,
alpha: Float = Float.NaN,
shadow: Shadow? = null,
textDecoration: TextDecoration? = null
){}
Both overload functions are the same, except for color and brush parameters to color the text.
Let’s first see how to create TextLayoutResult by TextMeasure.measure()
Here we’ll use LayoutModifier to create TextLayoutResult
.
val textMeasure = rememberTextMeasurer()
var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
Canvas(
modifier = Modifier
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
textLayoutResult = textMeasure.measure(
AnnotatedString("Text on Canvas!"),
style = TextStyle(
// text styling
)
)
layout(placeable.width, placeable.height) {
placeable.place(0, 0)
}
}
) { //draw }
here, textMeasure.measure
takes an annotated string and other configurations as an argument. Currently, there’s no way to pass a simple text string.
Now let’s use the above created textLayoutResult
to draw text
@OptIn(ExperimentalTextApi::class)
@Composable
fun ExampleTextLayoutResult() {
val textMeasure = rememberTextMeasurer()
var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
Canvas(
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.layout { measurable, constraints ->
...
}
) {
drawRect(color = Color.Black)
textLayoutResult?.let {
drawText(
textLayoutResult = it,
alpha = 1f,
shadow = Shadow(color = Color.Red, offset = Offset(5f, 8f)),
textDecoration = TextDecoration.Underline
)
}
}
}
Along with textLayoutResult
we have also customized our text. Let’s see the result
Easy. isn’t it?
Like Text
composable, you can decorate and customize text according to your requirements
For example, you can set overflow when text is too long to fit
@OptIn(ExperimentalTextApi::class)
@Composable
fun ExampleTextOverFlow() {
val textMeasure = rememberTextMeasurer()
Canvas(
onDraw = {
drawRect(color = Color.Black)
drawText(
textMeasurer = textMeasure,
text = //...some long string,
overflow = TextOverflow.Ellipsis,
maxLines = 3
)
})
}
Here text is ellipsed to fit in the container
That’s it for today, hope you have a basic idea of drawText API. It’s effortless to use. You can do lots of customization to decorate your text on canvas. Let’s wait for Experimental Tag to be removed from API, till that keep exploring it 🍻.
The complete source code of the above examples is available on Github.
Hope you found this blog post useful.
Thank you!!