Dot. Dash. Design

Mastering Lines in Jetpack Compose with PathEffect

Katie Barnett
ProAndroidDev

--

Pretty morse code perhaps?

Often when creating a UI in Jetpack Compose we need to draw lines. Sometimes as dividers to break up some space or aesthetically as part of more complex shapes or designs. We don’t always want them just to be a straight unbroken line either, using dots or dashes may give a less harsh effect than a unbroken line would and using fun shapes or styled line endings can give design refinement.

If you want your lines to show a morse code of dots and dashes then read on to decode the secrets of styling lines!

The Basics.

Just to make sure we’re on the same page, here is how to draw a basic line in Jetpack Compose:

Here, the line is horizontal, is inside a canvas and has a start and end Offset. The strokeWidth is set to a pixel value (in my case, converted from dp) and we can set a colour. You can apply more interesting colour effects using a version that takes a Brush instead of a solid colour.

A very basic, boring straight line.

The endpoint coordinates start from (0,0) in the top left and bottom right is the canvas (width,height) point.

Source: https://developer.android.com/jetpack/compose/graphics/draw/overview

For a basic primer on how to drawing in Canvas works in Jetpack Compose and DrawScope, you can check out the official documentation.

Now that we have a line, how can we make it a little more polished?

Stroke caps

The first thing we can do is apply a more refined stroke cap to the line.

Our options here are StrokeCap.Butt, the default, no extension, just square ends, StrokeCap.Rounded which extends the line with a semicircle and StrokeCap.Square which also extends with a square shape (which looks the same as Butt but for the extension.

StrokeCap.Butt: black line, StrokeCap.Rounded: red line & StrokeCap.Square: blue line

It is important to note that these extensions will go outside of the Canvas bounds, in the image above you can see that the red & blue lines are longer than the black line even though their Canvas composables are the same size.

These stroke caps can be applied using the cap argument on the line:

Let’s go for something a bit more dashing…

Now the basics are under control, now we can make use of PathEffect to make the lines more interesting.

Here, we can a dashPathEffect which allows you to specify the amount of time to dash on (show the line) and dash off (don’t show the line). You can set these intervals however you like. They also use pixel values so it is good to use with(LocalDensity.current) to convert dp into px to account for the specific device screen density.

You may notice the phase value, this indicates where the line should start drawing from, zero will start with drawing the first dash on, other values will allow you to start at different points.

Phase: 0, dashOnInterval/2 and dashOnInterval. Here I have set the cap back to StrokeCap.Butt to show the phase effect more clearly.

Morse code

Here is where we can get a bit more complex with dashes by adding more intervals to the dashPathEffect:

Giving a nice interesting line:

Stamping time

What if you don’t want your line to be made up of other lines? Instead we can stamp a specific shape along the line using stampedPathEffect. Here, we construct the shape desired, and set up the spacing between shapes (the advance, using a phase as before and a StampedPathEffectStyle:

With a circle shape to stamp we have a line of circular dots!

StampedPathEffectStyle.Translate will just move along the line and keep displaying the shape the same way up it was defined. The other types of StampedPathEffectStyle we’ll get to later, they don’t really apply when we’re looking at a straight line.

For something a bit more fancy, just make the shape fancy:

Thank-you to Radhika S for the heart path in their blog post.
😍

Zig Zags? Waves?

So how might we go about making this kind of line:

So you could just create a path, and have a loop that calculates and draws all the zig zags as points or, just create this path shape once, then stamp this path as we have done above.

In the first attempt, we create the shape and stamp the shape along the line (using an advance of the same width as the entire shape

This gives an unexpected (although not unpleasing) result:

Not quite what we wanted!

Usually, if we were drawing this as a path, we could just use drawPath with a style of DrawStyle.Stroke and we would have zig zags as lines. But because we are using this as a shape, it is automatically filled. To fix this, we need to close the path:

Putting this into the line we get:

So you may notice here that the actual line is drawn from point (0,0) so all the zig zag shape hangs below and extends from the end of the actual line. This can be more easily seen if we also add in the straight line path:

If you are after precision, you can add a translation to your path using translate with an Offset so the path is drawn exactly over the desired line:

Giving the desired result:

The line does extend beyond the horizontal edges, you can further fix this by adding a clipping box to the Canvas if desired.

The full code for the zig zag line:

Of course, not just straight lines!

Now we have mastered drawing straight lines! What if you want to put this into a shape? Well then just use drawRect or drawCircle or drawPath to get the shape you want:

Check out my github for the example code for these

What happens at the corners?

I mentioned earlier about StampedPathEffectStyle, this is useful for when you are not just drawing straight lines. This tells the stamped path what to do as the line changes direction.

For example, we can see the different styles using the heart path:

StampedPathEffectStyle.Translate, StampedPathEffectStyle.Rotate, and StampedPathEffectStyle.Morph

Translate is a direct translation, and because I didn’t fix the offset for the heart shape it means my shape is not following the exact rectangle outline, it overhangs. The Rotate style rotates the shape around which fixes that overhang but we get some hearts that are closer and some further away from each other. For Morph, it is a bit easier to see with the zig zag path:

Again, StampedPathEffectStyle.Translate, StampedPathEffectStyle.Rotate, and StampedPathEffectStyle.Morph

Here, the difference is more pronounced, while the rotate does okay for the heart path, for the zig zag where the stamped shapes are connected the gaps are obvious in the corners (and in some places overlapping). Morph fixes this by transforming the shape as it goes around corners and (mostly — see bottom left corner) giving a smoother line effect. You can kind of see this with the heart path as well as the hearts in the corners are a little smaller to allow them to transition around the corner.

Lines. Mastered.

Now you can see many ways you can style your lines in Jetpack Compose and how you can use them in interesting shapes and paths.

Go make some fun morse code with dots and dashes in your lines and shapes and send secret messages to your users!

You can find all the sample code used here in my Github experiments:

The eagle eyed may have noticed that I didn’t discuss PathEffect.chainPathEffect. To be completely honest I could not construct a good example use of it and could not find any other examples where others have used it (I even tried Gemini!) If you do see a good example, please share it with me.

Hopefully one day I’ll come back to this with a part two just for chainPathEffect!

--

--