One of the most powerful widgets in the Flutter catalog (and for me, one of the most underrated) is the Transform widget. Transform widgets allow us to fundamentally alter how widgets look and behave, allowing us to create new, complex types of animations. Underneath the Transform widget, a 4D matrix powers the actual transformation — defined by the Matrix4 class. While Flutter provides easy ways to do transformations such as translation, scaling, and rotation, we can use the Matrix4 to create even more awesome things such as 3D perspective transformation.
In this article, we are going to explore the 4D matrix itself and what the individual values in it do. In the previous Transform Deep Dive I wrote, we discussed how to use Transforms without directly interacting with a 4D matrix. Check it out if you need a refresher on the easier uses of the widget.
Even though anything with “4D” attached to it sounds cool by default, in reality, a 4D matrix is simply a matrix with 4 rows and 4 columns. We need to use a 4D matrix to transform an object in 3 dimensions (Here, the dimensions are what we’re used to: length, breadth and height).
Identity Matrix
This formation of the matrix is called an identity matrix. The best way to think of an identity matrix is that this is the equivalent of the number ‘1’ in the matrix form — It leaves things untouched when used to transform widgets.
Using different combinations of numbers in this matrix, we can manipulate the shape, size, orientation, etc of a given object.
Let’s look at how we do this.
Let’s take a look at the code that we are going to use for experimentation:
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double x = 0;
double y = 0;
double z = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Transform(
transform: Matrix4(
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1,
)..rotateX(x)..rotateY(y)..rotateZ(z),
alignment: FractionalOffset.center,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
y = y - details.delta.dx / 100;
x = x + details.delta.dy / 100;
});
},
child: Container(
color: Colors.red,
height: 200.0,
width: 200.0,
),
),
),
),
);
}
}
The code simply uses a Transform widget and a colored Container to transform. We define an identity Matrix4 for the starting point which does… well… nothing at all. The rest of the code with the GestureDectector allows us to rotate the square in X and Y directions. The code for rotating the square is final and we do not intend to make any changes to it. If you need more information on what we’re doing, I suggest looking at Wm Leler‘s article on Perspective in Flutter where a similar method is used. The x, y, and z variables simply track the amount of rotation that has already been done.
We will come back to why we’re allowing the user to rotate the square later, for now, we focus on the matrix and basic 2D results.
Note 1: The line
alignment: FractionalOffset.center,
sets the centre of the transformation to the centre of the square.
Note 2 (for nerds): Matrix4 is in column-major order by default. In the way the code is written, we effectively write it in row-major format. Hence, all the row and column values written will be inverted along the diagonal.
Here’s how the screen looks right now:
Very interesting square we have there. Let’s see if we can do something with it.
#flutter