When it first clicked for me that Flutter gives me full control of everything on the screen, right down to the last pixel, I immediately wanted to go beyond the fixed and predictable world of most app UIs. The fully custom widgets that I could draw using CustomPaint begged to be broken out of the typical ScrollView or Container. I wanted to move around, zoom in on things, and navigate the world that Flutter makes it possible to create. The Transform widget was my vehicle for navigating this world, and GestureDetector provided the controls.

GestureDetector gives easy access to most gestures, and Transform provides the ability to modify the perspective in which a child widget is seen. Both are simple, independent widgets that follow Flutter’s pattern of composability. When we combine the two, every gesture on a widget becomes a way to explore.

An introduction to transformation

Despite its power, all that the Transform widget really does is to take a transformation matrix and apply it to its child. This results in the translation, scale, rotation, and even skew of the child with respect to the parent, all specified by one simple Matrix4 object.

Matrices are very convenient to work with when doing transformations because they are composable. Storing separate translation, scale, rotation, and skew parameters is intuitive at first, but it’s easy to run into ambiguity with things like order.

Imagine the user performs a series of gestures back to back. If the gestures are simply a series of drags, then we can easily store the final transformation as the resulting location Offset. However, if the user drags, then rotates at the new location, and repeats this several times, how do we keep track of the final state? An offset and a double of radians is not enough, not even if we include a focal point as well. We would have to keep track of an ever expanding list of operations that the user has performed and iterate the whole thing just to get to the final state.

A transformation matrix can be combined with any number of other transformation matrices indefinitely, and the result is always another single matrix of the same size. The GPU is very fast at doing this sort of math, even massively in parallel, such as for every pixel on the screen. This makes it great for storing the state of a widget like Transform, and the result is a very straightforward widget for Flutter developers to use.

Detecting gestures

If we tie some user gestures into updating the transformation matrix using a GestureDetector, then the user has the freedom to navigate the scene that we’re displaying to them. GestureDetector provides easy access to gestures like drag, which we can use for translation, pinch, for zooming, and even a two-finger rotation.