Rotation examples with Flutter Transform (Part I)

Alfonso García Santiago
8 min readFeb 22, 2021

In this story, you will find many interesting examples to learn how to rotate a widget using the Transform widget.

Table of Contents

Definition

Rotation around Z-axis (rotation in XY-plane)
• Two alternatives
• Angle of rotation
• Alignment and origin of rotation
• Origin by default
• Before rotating…
• Rotate with origin in the top-left corner
• Rotate with origin in the top-right corner
• Rotate with origin in the center

Rotation around X-axis or Y-axis (3D perspective)
• Visual explanation
• How can we get in Flutter this 3D view of a rotated shape?
• Examples of different values of the angle of rotation
• Pulling the door, pushing the door
• Playing with the perspective parameter

Definition

Transform.rotate creates a widget that transforms its child using a rotation around the center.

Rotation around Z-axis (rotation in XY-plane)

Two alternatives

To rotate around Z-axis (in XY-plane), we have two alternatives:

  • Use Transform widget with Matrix4.rotationZ(double radians) as the transform property value:
Transform(
transform: Matrix4.rotationZ(0.3),
alignment: Alignment.topLeft,
child: Container(
color: Colors.purpleAccent,
width: 200,
height: 200,
),
)
  • Use Transform.rotate constructor:
Transform.rotate(
angle: 0.3,
alignment: Alignment.topLeft,
child: Container(
color: Colors.purpleAccent,
width: 200,
height: 200,
),
)

Angle of rotation

The angle gives the rotation in clockwise radians.

If we use the Transform.rotate constructor, the angle is a property.

Otherwise, in the Transform widget with Matrix4.rotationZ(double radians), the angle is a parameter in Matrix4.rotationZ.

Alignment and origin of rotation

The alignment controls the origin of the rotation.

According to this, alignment: Alignment.topLeft would put the origin of the rotation in the top-left corner.

We can set the origin through two properties:

  • alignment property, e.g. alignment: Alignment.topLeft
  • origin property, e.g. origin: Offset(100,50)

Origin by default

  • By default, the origin is the top-left corner WHEN we use the transform property with Matrix4.rotationZ(double radians) value.
  • By default, the origin is the center WHEN we use the Transform.rotate constructor.

Before rotating…

Container( //grey background container (hidden here)
margin: EdgeInsets.only(top: 60, left: 60),
color: Colors.grey,
width: 200,
height: 200,
child: Container(
color: Colors.purpleAccent,
width: 200,
height: 200,
),
)

Rotate with origin in the top-left corner

Using the transform property, the origin is in the top-left corner by default so:

  • alignment: Alignment.topLeft = origin: Offset(0,0)
Container( //grey background
margin: EdgeInsets.only(top: 60, left: 60),
color: Colors.grey,
width: 200,
height: 200,
child: Transform(
alignment: Alignment.topLeft, //origin: Offset(0,0)
transform: Matrix4.rotationZ(0.3),
child: Container(
color: Colors.purpleAccent,
width: 200,
height: 200,
),
),
)

Using the Transform.rotate constructor, the origin is in the center by default so:

  • alignment: Alignment.topLeft = origin: Offset(-w/2, -h/2) that in this case, is origin: Offset(-100, -100)
Container(
margin: EdgeInsets.only(top: 60, left: 60),
color: Colors.grey,
width: 200,
height: 200,
child: Transform.rotate(
angle: 0.3,
alignment: Alignment.topLeft, //origin: Offset(-100, -100)
child: Container(
color: Colors.purpleAccent,
width: 200,
height: 200,
),
),
)

Rotate with origin in the top-right corner

Using the transform property, the origin is in the top-left corner by default so:

  • alignment: Alignment.topRight = origin: Offset(w,0) that in this case, is origin: Offset(200, 0)
Container(
margin: EdgeInsets.only(top: 60, left: 60),
color: Colors.grey,
width: 200,
height: 200,
child: Transform(
alignment: Alignment.topRight, //origin: Offset(200, 0)
transform: Matrix4.rotationZ(0.3),
child: Container(
color: Colors.purpleAccent,
width: 200,
height: 200,
),
),
)

Using the Transform.rotate constructor, the origin is in the center by default so:

  • alignment: Alignment.topRight = origin: Offset(w/2, -h/2) that in this case, is origin: Offset(100, -100)
Container(
margin: EdgeInsets.only(top: 60, left: 60),
color: Colors.grey,
width: 200,
height: 200,
child: Transform.rotate(
angle: 0.3,
alignment: Alignment.topRight, //origin: Offset(100, -100)
child: Container(
color: Colors.purpleAccent,
width: 200,
height: 200,
),
),
)

Rotate with origin in the center

Using the transform property, the origin is in the top-left corner by default so:

  • alignment: Alignment.center = origin: Offset(w/2, h/2) that in this case, is origin: Offset(100, 100)
Container(
margin: EdgeInsets.only(top: 60, left: 60),
color: Colors.grey,
width: 200,
height: 200,
child: Transform(
alignment: Alignment.center, //origin: Offset(100, 100)
transform: Matrix4.rotationZ(0.3),
child: Container(
color: Colors.purpleAccent,
width: 200,
height: 200,
),
),
)

Using the Transform.rotate constructor, the origin is in the center by default so:

  • alignment: Alignment.center = origin: Offset(0,0) that in this case, is origin: Offset(0,0)
Container(
margin: EdgeInsets.only(top: 60, left: 60),
color: Colors.grey,
width: 200,
height: 200,
child: Transform.rotate(
angle: 0.3,
alignment: Alignment.center, // origin: Offset(0, 0)
child: Container(
color: Colors.purpleAccent,
width: 200,
height: 200,
),
),
)

Rotation around X-axis or Y-axis (3D perspective)

Visual explanation

Consider the next 3D scene in which we will rotate around the X-axis a blue shape in the XY-plane.

This is the shape contained in the XY-plane before being rotated around the X-axis.

And the projection in the XY-plane of the rotated shape is the projection in the XY-plane of the yellow shape. Notice that this is smaller than the original shape (the blue one) in the same plane, XY-plane.

The following is the interesting part: the 3D perspective of a rotated shape. This is how we really see a rotated shape where we see bigger the closest side of the shape and see smaller the furthest one.

How can we get in Flutter this 3D view of a rotated shape?

Starting with the original shape before being rotated:

Container(
margin: const EdgeInsets.only(left: 40, top: 80),
width: 250,
height: 160,
color: Colors.lightBlue,
)

If we rotate it directly by doing transform: Matrix4.rotationX:

Container(
margin: const EdgeInsets.only(left: 40, top: 80),
width: 250,
height: 160,
color: Colors.lightBlueAccent,

child: Transform(
transform: Matrix4.rotationX(-0.7),
alignment: Alignment.topCenter,
child: Container(
width: 250,
height: 160,
color: Colors.lightGreen,
),
),
)

we get the shape rotates but viewed as its projection in the XY-plane:

So to get the 3D perspective of the rotated shape,

we will use transform: Matrix4.identity()..setEntry(3,2,0.001) before applying rotation. This operation defines the transformation's identity matrix and set to 0.001 the element at position (3,2) in the matrix. In practice, it creates a 3D perspective because it makes bigger the lengths near us and makes smaller the lengths far from us.

We are rotating -0.7 radians around the X-axis, and we want this axis to match the top side of the container, and we are mean this by this line:alignment: Alignment.topCenter

Container(
margin: const EdgeInsets.only(left: 40, top: 80),
width: 250,
height: 160,
color: Colors.lightBlueAccent,

child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateX(-0.7),

alignment: Alignment.topCenter,
child: Container(
width: 250,
height: 160,
color: Colors.amber,
),
),
)

Examples of different values of the angle of rotation

-0.7 radians (-40.10º):

.....
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateX(-0.7),
alignment: Alignment.topCenter,
.....

-1.1 radians (-63.02º):

.....
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateX(-1.1),
alignment: Alignment.topCenter,
.....

-1.4 radians (-80.21º):

.....
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateX(-1.4),
alignment: Alignment.topCenter,
.....

-1.7 radians (-80.21º):

.....
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateX(-1.7),
alignment: Alignment.topCenter,
.....

Pulling the door, pushing the door

Previous examples were about rotating around X-axis, giving a 3D perspective. Now we will give a similar 3D view but rotating around the Y-axis. For that, we will implement pulling-the-door and pushing-the-door effects.

First, we implement the doorway (parent container).

Container(  //doorway
margin: const EdgeInsets.only(left: 60, top: 60),
width: 200,
height: 400,
color: Colors.black87,
)

Then add the door container as the doorway container's child (initially hiding the doorway completely).

Container( //doorway
margin: const EdgeInsets.only(left: 60, top: 60),
width: 200,
height: 400,
color: Colors.black87,

child: Container( //door
width: 300,
height: 400,
color: Colors.orangeAccent,
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 30),

child: Container( // doorknob
decoration: BoxDecoration(
color: Colors.deepOrange,
shape: BoxShape.circle,
),
width: 30,
height: 30,
),
),
)

And finally, the pulling-the-door effect by rotating it around Y-axis (door left side) in 3D perspective:

Container( // doorway
margin: const EdgeInsets.only(left: 60, top: 60),
width: 200,
height: 400,
color: Colors.black87,

child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(0.7),
alignment: Alignment.centerLeft,


child: Container( // door
width: 300,
height: 400,
color: Colors.orangeAccent,
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 30),
child: Container(
decoration: BoxDecoration(
color: Colors.deepOrange,
shape: BoxShape.circle,
),
width: 30,
height: 30,
),
),
),
)

Besides using: rotateY() , notice we make the rotation around the left side of the door, doing:alignment: Alignment.centerLeft

And if we use a negative angle instead of a positive one, we get the effect of pushing the door:

Playing with perspective parameter.

We will call the value in position (3,2) of the transformation matrix the perspective parameter.

In the previous examples, we have used a value of 0.001 for this element:transform: Matrix4.identity()..setEntry(3,2,0.001)

As we have seen, this is value is the reason we see the 3D perspective.

Let’s see some examples with a fixed angle of rotation but changing the perspective parameter.

Before being rotated:

Fixed angle of rotation = -0.9 radians and a perspective param =0.001, 0.002, 0.003 and 0.004.

If you have found this article enjoyable or it has helped you in some way, give me many 👏.Thanks for reading me!

--

--