Pintor personalizado

En Compose, se usa un objeto Painter para representar algo que se puede dibujar (un reemplazo de las APIs de Drawable definidas en Android). Este objeto también influye en la medición y el diseño del elemento correspondiente componible que lo usa. Un BitmapPainter toma una ImageBitmap que puede dibujar un Bitmap en la pantalla.

En la mayoría de los casos de uso, el uso de painterResource() anterior muestra el pintor correcto para el recurso (es decir, BitmapPainter o VectorPainter). Para obtener más información sobre las diferencias entre los dos, lee la sección ImageBitmap vs. ImageVector.

Un Painter es diferente de un DrawModifier, que dibuja estrictamente dentro de los límites que se le otorgan y no influye en la medición ni en el diseño del elemento componible.

Para crear un pintor personalizado, extiende la clase Painter e implementa el método onDraw, que permite el acceso a DrawScope para dibujar gráficos personalizados. También puedes anular intrinsicSize, que se usará para influir en el elemento de componibilidad que contiene:

class OverlayImagePainter constructor(
    private val image: ImageBitmap,
    private val imageOverlay: ImageBitmap,
    private val srcOffset: IntOffset = IntOffset.Zero,
    private val srcSize: IntSize = IntSize(image.width, image.height),
    private val overlaySize: IntSize = IntSize(imageOverlay.width, imageOverlay.height)
) : Painter() {

    private val size: IntSize = validateSize(srcOffset, srcSize)
    override fun DrawScope.onDraw() {
        // draw the first image without any blend mode
        drawImage(
            image,
            srcOffset,
            srcSize,
            dstSize = IntSize(
                this@onDraw.size.width.roundToInt(),
                this@onDraw.size.height.roundToInt()
            )
        )
        // draw the second image with an Overlay blend mode to blend the two together
        drawImage(
            imageOverlay,
            srcOffset,
            overlaySize,
            dstSize = IntSize(
                this@onDraw.size.width.roundToInt(),
                this@onDraw.size.height.roundToInt()
            ),
            blendMode = BlendMode.Overlay
        )
    }

    /**
     * Return the dimension of the underlying [ImageBitmap] as it's intrinsic width and height
     */
    override val intrinsicSize: Size get() = size.toSize()

    private fun validateSize(srcOffset: IntOffset, srcSize: IntSize): IntSize {
        require(
            srcOffset.x >= 0 &&
                srcOffset.y >= 0 &&
                srcSize.width >= 0 &&
                srcSize.height >= 0 &&
                srcSize.width <= image.width &&
                srcSize.height <= image.height
        )
        return srcSize
    }
}

Ahora que tenemos nuestro Painter personalizado, podemos superponer cualquier imagen sobre nuestra imagen de origen de la siguiente manera:

val rainbowImage = ImageBitmap.imageResource(id = R.drawable.rainbow)
val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)
val customPainter = remember {
    OverlayImagePainter(dogImage, rainbowImage)
}
Image(
    painter = customPainter,
    contentDescription = stringResource(id = R.string.dog_content_description),
    contentScale = ContentScale.Crop,
    modifier = Modifier.wrapContentSize()
)

A continuación, se puede ver el resultado de la combinación de las dos imágenes con un pintor personalizado:

Pintor personalizado que superpone dos imágenes una sobre la otra
Figura 1: Pintor personalizado que superpone dos imágenes una encima de la otra

También se puede usar un pintor personalizado con Modifier.paint(customPainter) para dibujar el contenido en un elemento que admite composición de la siguiente manera:

val rainbowImage = ImageBitmap.imageResource(id = R.drawable.rainbow)
val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)
val customPainter = remember {
    OverlayImagePainter(dogImage, rainbowImage)
}
Box(
    modifier =
    Modifier.background(color = Color.Gray)
        .padding(30.dp)
        .background(color = Color.Yellow)
        .paint(customPainter)
) { /** intentionally empty **/ }