El API de Canvas nos facilita con tres funciones independientes efectuar las transformaciones de rotación, escalado y translación.
Dibujar en la parte superior un rectángulo sin aplicar transformaciones, luego dibujar el mismo rectángulo en medio de la pantalla y aplicar una rotación de 45 grados. En la parte inferior de pantalla mostrar otro rectángulo del mismo tamaño pero aplicarle un escalado que lo muestre con un tamaño del 50% con respecto al original.
Finalmente dibujar un círculo con centro en la coordenada (0,0) y radio de 50, seguidamente aplicar una translación de 50 píxeles a la derecha y 50 píxeles hacia abajo.
Crearemos un proyecto llamado: 'Compose30'
La interfaz visual a implementar debe ser similar a:

El código a implementar en Kotlin para obtener dicha funcionalidad es:
package com.tutorialesprogramacionya.compose30
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.drawscope.translate
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PantallaPrincipal()
}
}
}
@Composable
fun PantallaPrincipal() {
Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
val ancho = size.width
val alto = size.height
drawRect(
topLeft = Offset(ancho / 2 - ancho * 0.10f, 0f),
size = Size(ancho * 0.20f, alto * 0.20f),
color = Color.Red
)
rotate(degrees = 45f) {
drawRect(
topLeft = Offset(ancho / 2 - ancho * 0.10f, alto / 2 - alto * 0.10f),
size = Size(ancho * 0.20f, alto * 0.20f),
color = Color.Green
)
}
scale(scale = 0.5f) {
drawRect(
topLeft = Offset(ancho / 2 - ancho * 0.10f, alto - alto * 0.10f),
size = Size(ancho * 0.20f, alto * 0.20f),
color = Color.Blue
)
}
translate(left=50f,top = 50f) {
drawCircle(
center = Offset(x = 0f, y = 0f),
radius = 50f,
color=Color.Cyan
)
}
})
}
El primer rectángulo no se le aplican transformaciones, lo ubicamos en medio de la pantalla y ocupa un ancho y alto de 20% (damos dichos valores llamando a la función size):
@Composable
fun PantallaPrincipal() {
Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
val ancho = size.width
val alto = size.height
drawRect(
topLeft = Offset(ancho / 2 - ancho * 0.10f, 0f),
size = Size(ancho * 0.20f, alto * 0.20f),
color = Color.Red
)
Llamamos a la función rotate y le pasamos en el parámetro degrees el valor 45f, luego en la función lambda que le pasamos a rotate graficamos un rectángulo, igual que el anterior. Veremos que el mismo aparece rotado 45 grados:
rotate(degrees = 45f) {
drawRect(
topLeft = Offset(ancho / 2 - ancho * 0.10f, alto / 2 - alto * 0.10f),
size = Size(ancho * 0.20f, alto * 0.20f),
color = Color.Green
)
}
Al rectángulo de la parte inferior de la pantalla le aplicamos un escalado de 0.5 (valor mayor a 1 obtenemos una imagen más grande):
scale(scale = 0.5f) {
drawRect(
topLeft = Offset(ancho / 2 - ancho * 0.10f, alto - alto * 0.10f),
size = Size(ancho * 0.20f, alto * 0.20f),
color = Color.Blue
)
}
El círculo para evitar que quede parte fuera de la pantalla le aplicamos una translación de 50 píxeles en x e y:
translate(left=50f,top = 50f) {
drawCircle(
center = Offset(x = 0f, y = 0f),
radius = 50f,
color=Color.Cyan
)
}
Este proyecto lo puede descargar en un zip desde este enlace: Compose30.zip
Podemos hacer varias transformaciones sucesivas:
@Composable
fun PantallaPrincipal() {
val imagen=ImageBitmap.imageResource(id = R.drawable.fondo)
Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
val ancho = size.width
val alto = size.height
rotate(degrees = 45f) {
scale(scale=0.6f) {
drawImage(
image = imagen,
dstOffset = IntOffset(0, 0),
dstSize = IntSize(ancho.toInt(), alto.toInt())
)
}
}
})
}
En el ejemplo estamos escalando la imagen que ocupa toda la pantalla y rotando en 45 grados.
Teniendo como resultado:

Hay una solución más eficiente cuando tenemos que hacer transformaciones sucesivas. Debemos llamar a la función withTransform y pasar en la primera función lambda las transformaciones y en la segunda función lambda las funciones a las que se debe aplicar dichas transformaciones:
@Composable
fun PantallaPrincipal() {
val imagen=ImageBitmap.imageResource(id = R.drawable.fondo)
Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
val ancho = size.width
val alto = size.height
withTransform({
rotate(degrees = 45f)
scale(scale = 0.6f)
}) {
drawImage(
image = imagen,
dstOffset = IntOffset(0, 0),
dstSize = IntSize(ancho.toInt(), alto.toInt())
)
}
})
}