Drawing the image on canvas

We can use the DrawImage Composable to show a static image on screen. To make it constrained to a size, let’s wrap it in a Container:

So we get:

We can also add tinting and opacity to it:

But this is a static image. How do you rotate something with Compose?

Canvas transformations

The Draw Composable gives us a way to manipulate the canvas.

We can use it to draw directly on it, e.g.:

But we can also use it to apply transformations on the canvas to change how child Composables render themselves.

For example, this setup results in the child rendering slightly to the left:

Don’t forget to save and restore canvas state before / after invoking drawChildren so that your changes only affect the rendering of your child Composable.

We can use canvas transformations for rotations too!

Apply rotation

Let’s try to rotate the canvas by 45 degrees:

But this is what happens:

It’s even weirder when we change the rotation value to 180 degrees:

What’s going on here?

It looks like as though it’s not only rotating, but also adding an offset somehow?

This commit in the AOSP repo means we’ll be getting “show layout bounds” functionality for Compose soon. Until then, a simple visual debugging tool you can use whenever you feel stuck is to wrap the Composable in question in a Surface to visualise the box it is being rendered in:

In this case, adding a coloured background and trying the rotation with different values helps to understand that we’re rotating the canvas around its top-left corner ((0,0) coordinates), and not around its center as we might have expected it to:

To fix this, we can apply:

translation by half of the width and height of the child — this moves the imaginary (0,0) point to the center rotation reversing the translation — so that all further actions are done relative to the original (0,0) coordinate and not the center

In code:

Now it looks correctly rotated around its center with any arbitrary rotation value:

That’s great!

But if we could only apply a rotation once to create a static rotated image, it wouldn’t be much fun.

How do we make it animated?

A super quick intro to Transitions

Animations in Compose can be done with the Transition Composable, but — depending on where you’re coming from — it might not be fully intuitive how it works.

Transition doesn’t know about your UI, it operates on a more abstract level, and animates only values without making assumptions what they will be used for. (If you ever worked with ValueAnimator, it’s a similar concept). To demonstrate, let’s see something very basic without any UI parts first:

Transition works on a TransitionDefinition to get from one defined state to another one. All you do is set up keys and values associated to them — Transition will call the lambda you provided repeatedly with intermediate values between the states you defined.

The above example would print lines to the console similar to:

Of course, you are tied neither to the console, nor to just animating one float value!

The funny thing is that you define what keys you want to have, what kind of values do they even reflect, and how many different “endpoint” states you want to have.

You can have as many keys and states as you wish, giving you complete freedom to define any complex animation based on these values.

The androidx.animation package also provides you with other useful PropKeys too: IntPropKey, FloatPropKey, DpPropKey, ColorPropKey, PxPropKey, PxPositionPropKey.

In addition, as PropKey is an interface with generic <T> type, you can write your own interpolation for whatever other thing too.

Now that we know this, it shouldn’t be too difficult to apply it to rotate our image!

Rotate by transition

To animate the rotation value, we could create a TransitionDefinition such as:

And then passing this Transition, we’ll use the latest animated value for the canvas rotation inside our Draw block:

The above code results in this animation:

We’re getting there!

Final touches

Though on the above gif the animation is continuous, it’s only because the gif itself is looped. If we launched our code on a device, we would see that the animation actually finishes after a single rotation is complete and then does nothing else after that.

To fix this, let’s change the TransitionDefinition to:

Also, though tween is using a nice easing by default, in our case we don’t want that. Let’s just use a linear one:

The above changes result in this animation:

Generic solution

We can arrive at a generic solution by:

extracting a Rotate Composable that rotates the canvas by a given degree and draws another Composable once building on top of the previous, providing a RotateIndefinitely Composable that uses Transition to play the animation allowing duration to be parameterised

Here’s the complete solution:

And now client code becomes:

Of course, we can now rotate any other Composable too: