# Playing with Paths

I recently helped out with a hero animation in an app–unfortunately I can’t share this animation just yet… but I wanted to share what I learned making it. In this post I’ll walk through recreating this mesmerizing animation by Dave ‘beesandbombs’ Whyte which demonstrates many of the same techniques:

My first thought when looking at this (which might not be much of a surprise to anyone who knows my work) was to reach for an `AnimatedVectorDrawable`

(`AVD`

hereafter). `AVD`

s are great, but they’re not suitable for every situation — specifically we had the following requirements:

- I knew we’d need to draw a polygon, but we hadn’t settled on the exact shape.
`AVD`

s are “pre-baked” animations, as such varying the shape would require re-making the animation. - Part of the ‘progress tracking’ aspect, we’d want to only draw a portion of the polygon.
`AVD`

s are ‘fire-and-forget’ i.e. you can’t scrub through them. - We wanted to move another object around the polygon. This is definitely achievable with
`AVD`

s… but again would require a lot of upfront work to pre-calculate the composition. - We wanted to control the progress of the object moving around the polygon separately from the portion of the polygon being shown.

Instead I opted to implement this as a custom `Drawable`

, made up of `Path`

objects. `Path`

s are a fundamental representation of a shape (which `AVD`

s use under the hood!) and Android’s `Canvas`

APIs offer pretty rich support for creating interesting effects with them. Before going through some of these, I want to give a shout out to this excellent post by Romain Guy which demonstrates many of techniques which I build upon in this post:

## Polar coordination

Usually when defining 2d shapes, we work in (x, y) coordinates technically known as cartesian coordinates. They define shapes by specifying points by their distance from the origin along the x and y axes. An alternative is the polar coordinate system which instead defines points by an angle (θ) and a radius (r) from the origin.

We can convert between polar and cartesian coords with this formula:

`val x = radius * Math.cos(angle);`

val y = radius * Math.sin(angle);

I highly recommend this post to learn more about polar coordinates:

To generate regular polygons (i.e. where each interior angle is the same), polar coordinates are extremely useful. You can calculate the angle necessary to produce the desired number of sides (as the interior angles total 360º) and then use multiples of this angle with the same radius to describe each point. You can then convert these points into cartesian coordinates which graphics APIs work in. Here’s a function to create a `Path`

describing a polygon with a given number of sides and radius:

`fun createPath(sides: Int, radius: Float): Path {`

val path = Path()

val angle = 2.0 * Math.PI / sides

path.moveTo(

cx + (radius * Math.cos(0.0)).toFloat(),

cy + (radius * Math.sin(0.0)).toFloat())

for (i in 1 until sides) {

path.lineTo(

cx + (radius * Math.cos(angle * i)).toFloat(),

cy + (radius * Math.sin(angle * i)).toFloat())

}

path.close()

return path

}

So to recreate our target composition, we can create a list of polygons with different numbers of sides, radius and colors. `Polygon`

is a simple class which holds this info and calculates the `Path`

:

`private val polygons = listOf(`

Polygon(sides = 3, radius = 45f, color = 0xffe84c65.toInt()),

Polygon(sides = 4, radius = 53f, color = 0xffe79442.toInt()),

Polygon(sides = 5, radius = 64f, color = 0xffefefbb.toInt()),

...

)

## Effective path painting

Drawing a Path is simple using `Canvas.drawPath(path, paint)`

but the `Paint`

parameter supports a `PathEffect`

which we can use to alter *how* the path will be drawn. For example we can use a `CornerPathEffect`

to round off the corners of our polygon or a `DashPathEffect`

to only draw a portion of the `Path`

(see the ‘Path tracing’ section of the aforementioned post for more details on this technique):

An alternative technique for drawing a subsection of a path is to use

`PathMeasure#getSegment`

which copies a portion into a new`Path`

object. I used the dash technique as animating the`interval`

and`phase`

parameters enabled interesting possibilities.

By exposing the parameters controlling these effects as properties of our drawable, we can easily animate them:

object PROGRESS : FloatProperty<PolygonLapsDrawable>("progress") {

override fun setValue(pld: PolygonLapsDrawable, progress: Float) {

pld.progress = progress

}

override fun get(pld: PolygonLapsDrawable) = pld.progress

}...ObjectAnimator.ofFloat(polygonLaps, PROGRESS, 0f, 1f).apply {

duration = 4000L

interpolator = LinearInterpolator()

repeatCount = INFINITE

repeatMode = RESTART

}.start()

For example, here are different ways of animating the progress of the concentric polygon paths:

## Stick to the path

To draw objects along the path, we can use a `PathDashPathEffect`

. This ‘stamps’ another `Path`

along a path, so for example stamping blue circles along a polygon might look like this:

`PathDashPathEffect`

accepts `advance`

and `phase`

parameters — that is the gap between stamps and how far to move along the path before the first stamp. By setting the advance to the length of the entire path (obtained via `PathMeasure#getLength`

), we can draw a single stamp. By animating the phase (here controlled by a `dotProgress`

parameter [0, 1]) we can make this single stamp move along the path.

`val phase = dotProgress * polygon.length`

dotPaint.pathEffect = PathDashPathEffect(pathDot, polygon.length,

phase, TRANSLATE)

canvas.drawPath(polygon.path, dotPaint)

We now have all of the ingredients to create our composition. By adding another parameter to each polygon of the number of ‘laps’ each dot should complete per animation loop, we produce this:

You can find the source for this drawable here:

https://gist.github.com/nickbutcher/b41da75b8b1fc115171af86c63796c5b#file-polygonlapsdrawable-kt

## Show some style

The eagle eyed amongst you might have noticed the final parameter to `PathDashPathEffect`

: `Style`

. This enum controls how to transform the stamp at each position it is drawn. To illustrate how this parameter works the example below uses a triangular stamp instead of a circle and shows both the `translate`

and `rotate`

styles:

Notice that when using `translate`

the triangle stamp is always in the same orientation (pointing left) whilst with the `rotate`

style, the triangles rotate to remain tangential to the path.

There’s a final `style`

called `morph`

which actually *transforms* the stamp. To illustrate this behaviour, I’ve changed the stamp to a line below. Notice how the lines *bend* when traversing the corners:

This is an interesting effect but seems to struggle in some circumstances like the start of the path or tight corners.

Note that you can combine

`PathEffect`

s using a`ComposePathEffect`

, that’s how the path stamp follows the rounded corners here, by composing a`PathDashPathEffect`

with a`CornerPathEffect`

.

## Going on a tangent

While the above was all we need to recreate the polygon laps composition, my initial challenge actually called for a bit more work. A drawback with using `PathDashPathEffect`

is that the stamps can only be a single shape and color. The composition I was working on called for a more sophisticated marker so I had to move beyond the path stamping technique. Instead I use a `Drawable`

and calculate where along the `Path`

it needs to be drawn for a given progress.

To achieve this, I again used the `PathMeasure`

class which offers a `getPosTan`

method to obtain position coordinates and tangent at a given distance along a `Path`

. With this information (and a little math), we can translate and rotate the canvas to draw our `marker`

drawable at the correct position and orientation:

`pathMeasure.setPath(polygon.path, false)`

pathMeasure.getPosTan(markerProgress * polygon.length, pos, tan)

canvas.translate(pos[0], pos[1])

val angle = Math.atan2(tan[1].toDouble(), tan[0].toDouble())

canvas.rotate(Math.toDegrees(angle).toFloat())

marker.draw(canvas)

## Find your path

Hopefully this post has demonstrated how a custom drawable using path creation and manipulation can be useful for building interesting graphical effects. Creating a custom drawable gives you the ultimate control to alter and animate different parts of the composition independently. This approach also lets you dynamically supply values rather than having to prepare pre-canned animations. I was very impressed with what you can achieve with Android’s `Path`

APIs and the built in effects, all of which have been available since API 1.