
Animating Text along a Path in Jetpack Compose
How to position and rotate blocks of text on any path
In this article, we will learn how to draw some text on a path, inside a Compose canvas. This can be used to create fancy graphics, or animate a string of text in and out.
Final result with text animated along a path
Text setup
Let's first start with defining the text:
val text = remember { "Text on a Path" }
val style = remember {
TextStyle(
color = Lime400,
fontSize = 64.sp,
fontWeight = FontWeight.Bold,
)
}
In this code, we set the text we want on the path, and also create a TextStyle
. We need this style in multiple places, so it's best to define as a value for later.
Next we need to measure our text:
val textMeasurer = rememberTextMeasurer()
val result = textMeasurer.measure(text = text, style = style)
After creating a TextMeasurer
, we can measure our text by passing both our text
and style
. This will return a TextLayoutResult
which is calculated from applying the chosen font on to the text.
This result actually has way more details than we need. For now, we just want to know the position and size of each character.
Relevant position information from the measurement result
Draw the Text
With the text defined and measured is, we can now create a path and draw along it.
val path = Path()
// Define our path here, before measuring it
val measure = PathMeasure()
measure.setPath(path, false)
We first need to create a Path
and define how it looks using its provided functions.
After defining it, we need to measure the path using a PathMeasure
. This will give us extra information about the path, like its length or the position of any point along it.
Now we can iterate through each character in text
and draw it
val textWidth = result.getBoundingBox(text.lastIndex).bottomRight.x
text.forEachIndexed { index, char ->
val rect = result.getBoundingBox(index)
val distance = rect.left + ((measure.length - textWidth) * progress)
val pathOffset = measure.getPosition(distance)
drawText(
textMeasurer = textMeasurer,
text = char.toString(),
style = style,
topLeft = pathOffset - Offset(0f, rect.height * .5f),
size = Size(rect.width, rect.height)
)
}
First, we get the width of the entire text by checking the x position of the bottom right corner of the last character.
Next, we traverse through each character and calculate how far on the path it should be drawn.
The function getBoundingBox()
will give us the bounds of a single character. The left-most point of this bounds can be added to the available distance on the path, which is the total path length minus the text width (Note: Ensure your path length is longer than the length of your text).
Then, to animate along the path, we simply multiply this available distance by an animated float value (0f..1f
).
With distance
, we can get the offset of our character along the path using measure.getPosition
.
When drawing the text, we pass in this pathOffset
, minus half the height of the character so that it's centered on the path.
Other than the offset, we also pass in the textMeasurer
, the character itself, our defined TextStyle
and the size of the measured bounds of the character.
If we run this, we will get this result:
Text animation without individual character rotation
All the characters are translated properly, but we also need to rotate them according to the direction of the path. Luckily, PathMeasure
can also give us the tangent of any point along the path.
text.forEachIndexed { index, char ->
...
val rotation = measure.getTangent(distance).let { tan ->
(atan2(tan.y, tan.x) * (180 / PI)).toFloat()
}
rotate(
degrees = rotation,
pivot = pathOffset,
) {
drawText(
...
)
}
}
Using the tangent, we can calculate the rotation in degrees using some basic trigonometry.
Using rotation
, we can wrap our drawText
with a rotate
block and pass in the degrees we calculated. We also pass in the pathOffset
as the pivot, so that the character rotates around its center point.
Text animated along a path with correct rotation
And now, our text is animating along the path, with correct positioning and orientation.
Thanks for reading and good luck!
Subscribe for UI recipes