avatarElye

Summary

The article explains how to draw a custom map marker in Jetpack Compose using Path and arcTo functions.

Abstract

The article discusses the process of drawing a custom map marker in Jetpack Compose using Path and arcTo functions. The author explains the concept of arcTo function, which is used to draw an arc in a path, and provides examples of how to use it. The author also explains the use of Path to draw the entire marker as one path instead of calling multiple drawing functions. The article includes code snippets and images to illustrate the process.

Opinions

  • The author believes that using Path to draw the entire marker as one path instead of calling multiple drawing functions is a better approach.
  • The author suggests that understanding the concept of arcTo function is essential to draw a custom map marker in Jetpack Compose.
  • The author provides examples of how to use the arcTo function and Path to draw a custom map marker in Jetpack Compose.
  • The author includes code snippets and images to illustrate the process, which can be helpful for readers who are new to Jetpack Compose or drawing custom markers.
  • The author mentions that the code provided in the article is for drawing into a normal Bitmap Canvas instead of Jetpack Compose canvas, as originally designed for MapView which requires a BitmapDescriptor descriptor to draw its marker.

Learning Android Development

Understand Drawing Arc of A Path In Jetpack Compose Canvas

Custom Make A Graphical Component with Rounded Side on Jetpack Compose

Photo by Davit Simonyan on Unsplash

This month, I wrote an article showing Map View on Jetpack Compose. In it, I draw a custom map marker like a cartoon conversation dialog box shown below.

How do I draw this cartoon-style dialog for the marker? We have available functions that draw Circles, Ovals, Rectangle, and even Rounded Rectangle. Still, none of them fit a semi-circle rectangle, with a pointy, like cartoon- dialog as shown above. Hence we’ll have to draw ourselves using primitive path drawing.

I have previously explored all Canvas drawing functions as below.

However, since I like to have everything drawn together, I’m going to use Path to draw out everything in the cartoon dialog instead.

So I made a simple one as below, so I can fill it, or draw its border easily as drawing one path instead of calling multiple drawing functions.

Path drawing idea

The simple idea is as below, having a path of 7 path points.

A lineTo and moveTo are easily understood in Path.

A simple Canvas Path drawing code as below

Canvas(
    modifier = Modifier
        .fillMaxWidth()
        .height(height)
        .background(Color.Yellow)
) {
    drawPath(
        Path().apply {
            moveTo(1f, 1f)
            lineTo(4f, 3f)
            moveTo(6f, 3f)
            lineTo(8f, 1f)
            lineTo(12f, 3f)
        },
        Color.Black,
        style = Stroke(2.dp.value)
    )
}

But for arcTo provided, it is different, as it doesn’t have any starting or ending coordinate of the arc we can provide.

Arc drawing function

If we look at arcTo function

    fun arcTo(
        rect: Rect,
        startAngleDegrees: Float,
        sweepAngleDegrees: Float,
        forceMoveTo: Boolean
    )

Start Angle Degrees

The angle starts at the most right side, as 0°. Then move clockwise as it goes to 90° till it reaches 359°. (of course one can also have a negative value, to move anti-clockwise)

Sweep Angle Degrees

After having the start angle degree, next, we’ll define the sweep angle degree. This is to start drawing. The bigger the degree, the longer the arc will be.

Below are 3 examples

Of course, one can have negative sweep angles. This is for one to draw anti-clockwise. The direction of the drawing will determine how the arc will connect to its previous path (link to the beginning of the arc) and next path (link from the end of the arc).

Rect

This is simply where to position the Arc.

If we have a square rectangle, the arc will match well with a semi-circle shape as below.

But we can also have an elongated rectangle, where we will draw an arc as part of an oval.

Force Move to

This is a little trickier part.

Since arcTo doesn’t have a start arc point, as it is determined by the start degree, in a path, we usually want it to be connected to its previous path.

Assuming we have a path that draws as below with forceMoveTo = true

moveTo(1, 1)
lineTo(6, 1)
arcTo(Rect(5, 1, 10, 6), 0, 180, true)

It will draw as below. This is because, after drawing the line, it will moveTo draw the arc, without connecting the line and the arc.

However, if we set the forceMoveTo = false

moveTo(1, 1)
lineTo(6, 1)
arcTo(Rect(1, 1, 12, 6), 0, 180, false)

It will then draw a line connecting the end of lineTo to the start of arcTo as shown below

The reason the line is connected there, it’s because it is connected to the start of the Arc

So, if we want to connect the other point of the arc instead, we have to reverse or arc draw. Instead of starting drawing from 0°, we will start drawing from 180°, and draw in the reverse direction with a sweep angle of -180°.

Drawing the Cartoon Dialog Marker.

With the above understood, we can now easily draw the cartoon dialog marker, with the code below.

Here I’m drawing the into a normal Bitmap Canvas instead of Jetpack Compose canvas, as originally I design this for MapView which requires a BitmapDescriptor descriptor to draw its marker.

private fun setCustomMapIcon(message: String): Bitmap {
    val height = 150f
    val widthPadding = 80.dp.value
    val width = max(200f, paintTextWhite.measureText(message, 0, message.length) + widthPadding)
    val roundStart = height/3
    val path = android.graphics.Path().apply {
        arcTo(0f, 0f,
            roundStart * 2, roundStart * 2,
            -90f, -180f, true)
        lineTo(width/2 - roundStart / 2, height * 2/3)
        lineTo(width/2, height)
        lineTo(width/2 + roundStart / 2, height * 2/3)
        lineTo(width - roundStart, height * 2/3)
        arcTo(width - roundStart * 2, 0f,
            width, height * 2/3,
            90f, -180f, true)
        lineTo(roundStart, 0f)
    }

    val bm = Bitmap.createBitmap(width.toInt(), height.toInt(), Bitmap.Config.ARGB_8888)
    val canvas = android.graphics.Canvas(bm)
    canvas.drawPath(path, paintBlackFill)
    canvas.drawPath(path, paintWhite)
    canvas.drawText(message, width/2, height * 2/3 * 2/3, paintTextWhite)
    return bm
}

With this, you can easily understand how the below are linked

To get to MapView with this marker, you can get the design from the article

To explore drawing Arc, as shown above, you can get the design here.

Interested to learn more sophisticated Path drawing with bezier, check out

Jetpack Compose
Android App Development
AndroidDev
App Development
Mobile App Development
Recommended from ReadMedium