How to rotate an object using CustomPaint / Canvas in Flutter

How to rotate an object using CustomPaint / Canvas in Flutter
No time to read?
tl;dr
  • Rotation of widgets can be applied using the Transform. The easiest usage is the named constructor Transform.rotate()
  • Rotating objects on canvas is not directly possible. Instead, we need to apply the rotation to the whole canvas and draw the object onto that
  • The respective reusable function is described under coding

It’s a common task to rotate objects on the screen in a certain angle. While widgets can be wrapped with the Transform widget in order to perform the rotation, it’s not that easy for objects being drawn on Canvas.

Since we have no widget tree there and everything is a little bit more low-level, we need to perform the rotation ourselves.

How to rotate widgets

Let’s give the whole topic a shallow, pleasant start by having a look at how rotation works with widgets.

Like I said, the general way for transformations of widgets inside the widget tree is the Transform widget.

It can be used like this:

 1Transform(
 2  angle: bacteria.rotation,
 3  transform: Matrix4.rotationZ(
 4	  0.25
 5  ),
 6  child: Container(
 7    width: 24,
 8    height: 24,
 9    color: Colors.black,
10  ),
11);

This sounds more complicated than it is. The only things you need to remember are the following:

  • If you want to know the radian angle based on the angle in degree, just calculate degree * π / 180.
    If you have the angle as number of full circles (e. g. 1 is 360°, 1/2 is 180°), you can just calculate full_circle_amount * 2π
  • Luckily, you can apply the same to the Matrix4 and don’t have to create your matrix manually. The named constructor Matrix4.rotationZ() expects the angle in radians as well

And there is even a named constructor of the Transform widget that completely abstracts from matrices: Transform.rotate().
Use it like this:

1Transform.rotate(
2  angle: 0.25,
3  child: Container(
4    width: 24,
5    height: 24,
6    color: Colors.black,
7  ),
8);

Why use canvas

If there are widgets for the very task we are trying to accomplish, then why even bother with the low-level canvas, you might ask yourself.

Although Flutter has a lot of built-in performance optimizations, there are enormous differences between the widget tree and the canvas when it comes to speed.

This is mostly because widgets are on a higher abstraction level, carrying a lot more information than just how to be rendered. You can get an idea of the complexity by reading the article about BuildContext.

How to rotate objects using CustomPaint / Canvas

When using the canvas, things are a little bit more complicated. That’s because the objects that can be drawn on the canvas like Rect can not be rotated.

How do you rotate drawings if they do not provide an API for that?

An illustration

Let’s illustrate this with a real world example:

We assume we have a friend that is excellent at drawing. He has one problem, though: he can only draw his perfect objects upright.

Person drawing a Flutter logo
Our friend can only draw upright

Instead of making him draw the shape rotated, we just rotate the sheet of paper, let him draw and rotate it back.

The question is: how exactly do we rotate the paper? If we just rotate it using the upper left corner as the center of the rotation, the drawing may not even be on the sheet.
If we always use the center of sheet as the center of the rotation, the drawing might be displaced.

  1. Assuming the pen of the drawer is on the top left of the sheet (which it usually is), we need to move the sheet by the amount the object we want to have drawn on is displaced (relative to the top left of the sheet).
  2. Then we rotate the sheet around its center.
  3. We let the person draw its objects
  4. Afterwards we move the sheet back to where it was so that further drawings are not rotated anymore

Let’s illustrate how it looks if we want to draw the Flutter logo on the bottom right of the paper frame:

Person drawing a Flutter logo on a rotated sheet of paper
Instead of making our friend draw a rotated logo, we just rotate the sheet of paper

Coding

Okay, now we understood the reference of the real world. But how do we apply this to Dart code?

1canvas.save();
2canvas.translate(center.dx, center.dy);
3canvas.rotate(angle);
4canvas.translate(-center.dx, -center.dy);
5drawOurObject();
6canvas.restore();

Basically, we do something similar: before the rotation, we save the current canvas, because we want to restore it later.
After that, we do some translation so that the upcoming rotation actually the center of the object we want to draw.
Having rotated the canvas and translated back, we actually draw the object.
Finally, we need to restore the canvas, because we don’t want the transformation to be applied to all upcoming drawings.

Now we’d have a lot of code duplication if we copied and pasted these lines everywhere we want to rotate something, so let’s put it into a reusable code snippet:

 1void drawRotated(
 2  Canvas canvas,
 3  Offset center,
 4  double angle,
 5  VoidCallback drawFunction,
 6) {
 7  canvas.save();
 8  canvas.translate(center.dx, center.dy);
 9  canvas.rotate(angle);
10  canvas.translate(-center.dx, -center.dy);
11  drawFunction();
12  canvas.restore();
13}

That’s it! Because we want every possible drawing to be rotated, we expect a function instead of an object. There is no unified interface for every drawable object on the canvas like a draw() method.

Let’s see how the usage would look like:

 1final Rect rect = Rect.fromLTWH(
 2  object.x,
 3  object.y,
 4  object.width,
 5  object.height,
 6);
 7
 8drawRotated(
 9  canvas,
10  Offset(
11    object.x + (object.width / 2),
12    object.y + (object.height / 2),
13  ),
14  math.pi / 2,
15  () => canvas.drawRect(roundedRectangle, paint),
16);

We’re assuming we have an object with the properties x, y, width and height.

We use our newly created drawRotated() function by providing the Canvas object, the center of the object the rotation amount - in this case 90 °, a fourth of a circle as a circle is 2 * pi.

Conclusion

Rotating object on Canvas is not as intuitive as it is in the widget tree. We can not apply a rotation to an object we want to draw.

However, we can just rotate the whole canvas and apply the resulting Matrix to it.

By having created the analogy of a rotated sheet of paper and a person that can draw only upright, we could improve our understanding.

Also, we have created a reusable function to make it work for everything drawn on Canvas.

Comment this 🤌

You are replying to 's commentRemove reference