Learn about Android Matrix transformations

Many years ago, in school I learned about matrices. I don’t remember exactly, but what I do remember is thinking, “But…… What did you do with that knowledge?”

Fast forward a few years and I started working as an Android developer and had to use ImageView’s scaleType – if you’ve looked at all the possible types, you’ve already noticed that one of them is matrix. For years, I’ve avoided it, using other sizing types or solving problems. However, a few weeks ago I was working on a design where the background image of the component should be aligned with the top left corner of the view without any scaling, as follows:

So I went ahead and added an ImageView and went through all the Scaletypes again – hoping for one THAT I missed, but when I tried using scaleType=”matrix” none of them were quite right, it fitted my bill perfectly. But why does this work? What does it actually do?

So I looked at the Matrix document:

The Matrix class contains a 3×3 Matrix for converting coordinates

HMM… Not very helpful. Luckily, I’m not the only one who doesn’t get it, as Arnaud Bos has written a great article detailing the math behind it (warning: if you’re going to read it – it might take a cup of coffee (or two)). If you get lost in the middle of that article, I can’t blame you – it’s complicated, but the good news is that you don’t have to understand math to be able to use Matrix (although it’s helpful.

How can we use itMatrix?

As I mentioned earlier, we have to set scaleType=”matrix” on the ImageView. But to actually use it, we must set imageMatrix in our code:

imageView.imageMatrix = Matrix().apply {
    // perform transformations
}
Copy the code

Now we have this – what can we do with it? Matrices support a number of different transformations such as Translate, Scale, rotate, and Skew. If these sound familiar, because they are (mostly) the same as the ones on View, animation, or canvas.

You will find that there is a set, pre version, for each operation. I’ll talk about this later, but for now we’ll just use the set version.

So, what can we do?

Translating (Moving)

Setting translation means moving the image to another location. All you need to do is call setTranslate using the x and y coordinates required on the Matrix:

val dWidth = imageView.drawable.intrinsicWidth
val dHeight = imageView.drawable.intrinsicHeight

val vWidth = imageView.measuredWidth
val vHeight = imageView.measuredHeight
setTranslate( round((vWidth - dWidth)* 0.5 f),round((vHeight - dHeight) * 0.5f)
)
Copy the code

In this example, we simply put drawable in the center of the View, which results in the same behavior as setting scaleType=” Center “on the ImageView. So let’s look at how ImageView does this:

mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5 f),
                         Math.round((vheight - dheight) * 0.5 f));
Copy the code

It’s exactly the same! So we don’t know that it has applied a matrix transformation.

Scaling

Scaling (as you might have guessed) defines the size of an image. You can define two values – one for the x axis and one for the y axis. However, by scaling, you can also set pivot points.

Pivot point defines the point at which the transformation will remain constant. By default it is 0, 0 – top left – meaning the image will extend to the right and down, leaving the top left unchanged – just like the GIF on the left.

If you want to scale the image from the center (as in the GIF on the right), you can set the axis to the center of the image, as shown below:

setScale(0.5 f.0.5 f, dWidth / 2f, dHeight / 2f)

Copy the code

This will scale the image to half its size, the pivot point in its center. If you only want to scale it from the top left corner, you can omit the last two arguments as follows:

setScale(0.5 f.0.5 f)
Copy the code

But you can do a lot more with zoom. If you provide a negative scale value, you can basically mirror the image around the axis (or both). Quite beautiful!

Rotation

You guessed it! By rotating, you can rotate the image. Here, we provide the Angle we want to rotate, and an optional pivot point similar to a scale.

SetRotate (45fDWidth /2fThe dHeight /2f)Copy the code

Rotate the image 45 degrees around the center of the image. If you rotate it -45 degrees, it will rotate to the left.

Skewing

Tilt is a shift you may not have heard of before. Tilting will stretch your image along an axis (or both), just like the GIF above. Let’s look at an example:

SetSkew (1f.0fDWidth /2fThe dHeight /2f)Copy the code

This skews the image by 1 on the X-axis (and around the center point), which is the width of the image, causing the image to tilt by 45 degrees, just like the GIF above.

Apply multiple transformations

We can now translate, scale, rotate, and skew images, but what if we want to combine them? The obvious thing might be to call multiple set methods in succession. However, only the last transformation will be applied – all previous transformations will be overwritten. This is because the set method basically resets the Matrix.

But as I mentioned earlier, there is also a POST version of each transformation. By using these, we can apply multiple transformations and really harness the magic of Matrix.

But what’s the difference with the Pre? It makes no difference which of the three versions is used for the first transformation, but it can make a big difference for any future transformation.

Suppose we want to transform the image to the center of the view and scale it to half its size. These two versions will have the desired effect:

val drawableLeft = round((vWidth - dWidth) * 0.5 f)
val drawableTop = round((vHeight - dHeight) * 0.5 f)
// Version 1
setTranslate(drawableLeft, drawableTop)
val (viewCenterX, viewCenterY) = vWidth / 2f to vHeight / 2f
postScale(0.5 f.0.5 f, viewCenterX, viewCenterY)
// Version 2
setTranslate(drawableLeft, drawableTop)
val (drawableCenterX, drawableCenterY) = dWidth / 2f to dHeight / 2f
preScale(0.5 f.0.5 f, drawableCenterX, drawableCenterY)
Copy the code

Notice that in the first release we used postScale and the center of the view, while in the second release we used preScale and Drawable centers.

With postScale, the scale conversion is applied after Translate. Since the image is already centered in the view, we must use the center point of the view as the pivot.

val (viewCenterX, viewCenterY) = vWidth / 2f to vHeight / 2f
postScale(0.5 f.0.5 f, viewCenterX, viewCenterY)
Copy the code

So review the example from the beginning – why does applying scaleType=”matrix” only work? Using the default Martix, the scale will be 1, pan, rotate, and tilt will be 0, so the image will be drawn in the upper left corner. So it’s exactly what I need!

The next time you have to lay out an image in a way that the default scaleType doesn’t work – try using Matrix!