Welcome to part 2 of Drawing and painting in Jetpack Compose. Today I will be taking a look at text. Given how easy it is to draw dots, lines, arcs and curves, printing some text should be piece of cake, right? Something like
@Composable
fun TextDemo() {
Canvas(modifier = Modifier.fillMaxWidth().preferredHeight(128.dp),
onDraw = {
drawText(Color.Red, center, "Hello Compose")
})
}
Sadly, though, there is no drawText()
.
That's too bad.
But wait, Android's native canvas class (android.graphics.Canvas
) does have several drawText()
versions, one of the simplest ones being drawText(String text, float x, float y, Paint paint)
. Let's see if we can utilize this.
Please recall that our onDraw()
lamba acts as an extension function of androidx.compose.ui.graphics.drawscope.DrawScope
. Hence, this interface and accompanying extension functions define what we can call inside our code. As it turns out there is drawIntoCanvas()
. This function allows us to draw directly into the underlying androidx.compose.ui.graphics.Canvas
. If we peek into its source code we find:
Finally.
So here is how we can draw text onto our canvas:
@Composable
fun TextDemo() {
Canvas(modifier = Modifier.fillMaxWidth().preferredHeight(128.dp),
onDraw = {
val paint = android.graphics.Paint()
paint.textAlign = Paint.Align.CENTER
paint.textSize = 64f
paint.color = 0xffff0000.toInt()
drawIntoCanvas {
it.nativeCanvas.drawText("Hello", center.x, center.y, paint)
}
})
}
Instead of passing a lambda to drawIntoCanvas()
you could also write drawContext.canvas.nativeCanvas.drawText()
. We'll see this in the next example. As we are using the native canvas, we need native ways to customize output. For example, to set an individual font, put the .ttf file in res/font. Please keep in mind that the filename must meet certain rules (lowercase letters, numbers and the underscore only).
@Composable
fun C64TextDemo() {
Canvas(modifier = Modifier.fillMaxWidth().preferredHeight(128.dp),
onDraw = {
drawRect(Color(0xff525ce6))
val paint = Paint()
paint.textAlign = Paint.Align.CENTER
paint.textSize = 64f
paint.color = 0xffb0b3ff.toInt()
paint.typeface = customTypeface
drawContext.canvas.nativeCanvas.drawText(
"HELLO WORLD!",
center.x, center.y, paint
)
})
}
customTypeface
is declared like this:
private lateinit var customTypeface: android.graphics.Typeface
And then initialized as follows:
customTypeface = resources.getFont(R.font.c64_pro_mono_style)
Please be careful not to mix platform classes with Compose functions, classes or interfaces. For example there is androidx.compose.ui.text.font.Typeface
, which of course cannot be used together with android.graphics.Paint
. To separate the composable and the old platform world, I chose a toplevel variable, but you could also pass it to your composables as needed. The wonderful C64 font can by the way be downloaded at style64.org.
Before I close this post, let's pay some more attention to styling. For example, to unterline your text, just add
paint.flags = Paint.UNDERLINE_TEXT_FLAG
Paint.STRIKE_THRU_TEXT_FLAG
, well, strikes through the text. Please remember that this is not Compose-specific but ordinary Android framework api.
As Jetpack Compose offers the Text()
composable, when would we use text on canvas? Generally I suggest to use Text()
whenever possible. If you recall my sin()
plotter, which has its own cartesian coordinate system, it may make sense, though, to add labels to its axes using the method I have shown you in this post.
What are your thoughts on using nativeCanvas
? Please share them in the comments.
Top comments (1)
The correct way to do this seems to be to create a
Paragraph
and then call itspaint
method, passing indrawContext.canvas
.The way documented in this article assumes Android, so it will not work on desktop.