We already used some canvas transformations on our custom view before, but let's revisit the Canvas operations we can use. First of all, let's see how we can concatenate these transformations. Once we've used a transformation, any other transformation we use will be concatenated or applied on top of our previous operations. To avoid this behavior, we've to call the save() and restore() methods we also used before. To see how transformations build on top of each other, let's create a simple example.
First, let's create a paint object on our constructor:
public PrimitiveDrawer(Context context, AttributeSet attributeSet) { super(context, attributeSet); paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setAntiAlias(true); paint.setColor(0xffffffff); }
Now, let's calculate the rectangle size based on the size of the screen on the onLayout() method:
@Override protected void onLayout(boolean changed, int left, int top, int right,
int bottom) { super.onLayout(changed, left, top, right, bottom); int smallerDimension = (right - left); if (bottom - top < smallerDimension) smallerDimension = bottom -
top; rectSize = smallerDimension / 10; timeStart = System.currentTimeMillis(); }
We also stored the starting time, which we will use it for a quick and simple animation afterwards. Now, we're ready to implement the onDraw() method:
@Override protected void onDraw(Canvas canvas) { float angle = (System.currentTimeMillis() - timeStart) / 100.f; canvas.drawColor(BACKGROUND_COLOR); canvas.save(); canvas.translate(canvas.getWidth() / 2, canvas.getHeight() / 2); for (int i = 0; i < 15; i++) { canvas.rotate(angle); canvas.drawRect(-rectSize / 2, -rectSize / 2, rectSize / 2,
rectSize / 2, paint); canvas.scale(1.2f, 1.2f); } canvas.restore(); invalidate(); }
We've first calculated the angle based on the amount of time that passed since the beginning. Animations should always be based on time and not on the amount of frames drawn.
Then, we draw the background, store the canvas state by calling canvas.save(), and perform a translation to the center of the screen. We'll base all transformations and drawings from the center, instead of the top left corner.
In this example, we'll draw 15 rectangles where each one will be increasingly rotated and scaled. As transformations are applied on top of each other, this is very easy to do in a simple for() loop. It is important to draw the rectangle from -rectSize / 2 to rectSize / 2 instead of 0 to rectSize; otherwise, it will be rotating from one angle.
Change the code line where we draw the rectangle to canvas.drawRect(0, 0, rectSize, rectSize, paint) to see what happens.
There is, though, an alternative to this method: we can use pivot points on the transformations. Both rotate() and scale() methods support two additional float parameters that are the pivot point coordinates. If we look at the source code implementation of scale(float sx, float sy, float px, float py), we can see it is simply applying a translation, calling the simple scale method, and applying the opposite translation:
public final void scale(float sx, float sy, float px, float py) { translate(px, py);
scale(sx, sy);
translate(-px, -py); }
Using this method, we could have implemented the onDraw() method this other way:
@Override protected void onDraw(Canvas canvas) { float angle = (System.currentTimeMillis() - timeStart) / 100.f; canvas.drawColor(BACKGROUND_COLOR); canvas.save(); canvas.translate(canvas.getWidth() / 2, canvas.getHeight() / 2); for (int i = 0; i < 15; i++) { canvas.rotate(angle, rectSize / 2, rectSize / 2); canvas.drawRect(0, 0, rectSize, rectSize, paint); canvas.scale(1.2f, 1.2f, rectSize / 2, rectSize / 2); } canvas.restore(); invalidate(); }
See the following screenshot to see how the rectangles are concatenated:
![](assets/8235837e-4edc-49f9-9f1e-842f8fe94de8.png)
In addition, the source code of this full example can be found in the Example21-Transformations folder on the GitHub repository.
We've seen some basic operations on matrices, such as scale(), rotate(), and translate(), but canvas provides us with some more additional methods:
- skew: This applies a skew transformation.
- setMatrix: This lets us compute a transformation matrix and directly sets it to our canvas.
- concat: This is similar to the previous case. We can concatenate any matrix to the current one.