Chaining transformation matrices

In a previous article, I showed how to do a slide-in animation. Each widget slid into place from below (or the side) using a Transform Widget in an AnimatedBuilder.

But what if you want to add a curve to the slide?

And that is what chaining transformation matrices is for.

If you have knowledge of transformation matrices already, you don’t need this article. Go read a good book with a cup of your beverage of choice next to you :) Woman on couch reading a book with a cup of hot chocolate next to her

Enjoy. [Created using Dall-E]

If not, you’ve come to the right place.

vines-separator

We will build on the code from the previous article. In our AnimatedTile, we used the Transform widget with Matrix4.translationValues. It allows us to specify the translation — a.k.a. movement — we want to move the widget x, y, and z.

// Wraps the child in a Transform with the given slide and
// an AnimatedBuilder with the given animation.
class AnimatedTile extends StatelessWidget {
  const AnimatedTile({
    super.key,
    required this.animation,
    required this.slide,
    required this.child,
  });

  final Animation<double> animation;
  final int slide;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: animation,
        child: child,
        builder: (context, child) {
          return Transform(
            transform: Matrix4.translationValues(
                0, (1.0 - animation.value) * slide, 0),
            child: Padding(padding: const EdgeInsets.all(8.0),
                           child: child),
          );
        });
  }
}

The Matrix4 class has many other helper functions to create transformations. And the cool thing about this is that you can chain them together by multiplying them. So if you want to add more to your slide-in, all you need to do is multiply the current transformation matrix by another helper function, and you’re done.

Rotation

The rotationX, rotationY and rotationZ functions rotate the widget around the given axis.

X-Axis Rotation:

The widget flips vertically, like a cat flap. Chained together with our slide in, it looks like this:

gif of rotation around the x axis

And the code is:

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
      animation: animation,
      child: child,
      builder: (context, child) {
        return Transform(
            transform: Matrix4.translationValues(
                    0, -(animation.value - 1) * slide, 0) *//this is the slide
                Matrix4.rotationX((1 - animation.value) * 1),//rotation X
            child: child);
      });
}

Here, our rotation goes between 1 radian to 0 radians.

Y-Axis Rotation:

This gives a horizontal flip, as if the widget is a door swinging shut.

gif of rotation around the y axis

And the code is:

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
      animation: animation,
      child: child,
      builder: (context, child) {
        return Transform(
            transform: Matrix4.translationValues(
                    0, -(animation.value - 1) * slide, 0) *//this is the slide
                Matrix4.rotationY((1 - animation.value) * 1),//rotation Y
            child: child);
      });
}

Z-Axis Rotation:

It’s like the widget is spinning on a record player.

gif of rotation around the z axis

And the code is:

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
      animation: animation,
      child: child,
      builder: (context, child) {
        return Transform(
            transform: Matrix4.translationValues(
                    0, -(animation.value - 1) * slide, 0) *//this is the slide
                Matrix4.rotationZ((1 - animation.value) * 0.1),//rotation Z
            child: child);
      });
}

Here, I used a smaller rotation, as 1 radian around the Z axis was too much.

Scaling

We can also add a scaling animation, giving the effect of growth, by multiplying with an additional helper function: Matrix4.diagonal3Values(). Here you specify the scaling in x, y, and z. So for a “growing” animation the values need to be the same on the x and y axes, and 0 in z. Chained with our slide-in and the rotation around the z-axis, it looks like this:

gif of rotation around the z axis and widgets expanding using matrix chaining

With the following code:

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
      animation: animation,
      child: child,
      builder: (context, child) {
        return Transform(
            transform: Matrix4.translationValues(
                    0, -(animation.value - 1) * slide, 0) *//slide
                Matrix4.rotationZ((1 - animation.value) * 0.1) *//rotate
                Matrix4.diagonal3Values(0.5 + animation.value * 0.5,
                    0.5 + animation.value * 0.5, 1),//scale
            child: child);
      });
}

Note the values in the function:

x and y are both 0.5 + animation.value * 0.5, that is, they go between 0.5 and 1. z is 1, signifying no change.

As you can see, you can play around to your hearts’ content, using an AnimatedBuilder, a Transform widget, and the endless possibilities of the Matrix4 helper functions.

Animate on.

vines-separator

98 days and still counting. #BringThemHome.

Check out my free and open source online game Space Short. If you like my stories and site, you can also buy me a coffee.