Painter personnalisé

Dans Compose, un objet Painter sert à représenter un élément qui peut être dessiné (il remplace les API Drawable définies dans Android) et à influencer la mesure et la mise en page du composable correspondant qui l'utilise. Un BitmapPainter prend un ImageBitmap qui peut dessiner un Bitmap à l'écran.

Dans la plupart des cas, l'utilisation de painterResource() ci-dessus renvoie l'objet Painter approprié pour l'élément (par exemple, BitmapPainter ou VectorPainter). Pour en savoir plus sur les différences entre les deux, consultez la section ImageBitmap et ImageVector.

Un objet Painter est différent d'un DrawModifier, qui dessine strictement dans les limites qui lui sont fournies et qui n'a aucune influence sur la mesure ni la mise en page du composable.

Pour créer un objet Painter personnalisé, développez la classe Painter et implémentez la méthode onDraw, qui permet d'accéder à DrawScope pour dessiner des éléments graphiques personnalisés. Vous pouvez également remplacer intrinsicSize, qui sera utilisé pour influencer le composable dans lequel il se trouve :

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
    }
}

Maintenant que nous avons notre Painter personnalisé, nous pouvons superposer n'importe quelle image sur notre image source comme suit :

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()
)

Le résultat de la combinaison des deux images avec un objet Painter personnalisé est illustré ci-dessous :

Painter personnalisé superposant une image au-dessus d'une autre
Figure 1 : Painter personnalisé superposant une image au-dessus d'une autre

Un objet Painter personnalisé peut aussi être utilisé avec Modifier.paint(customPainter) pour dessiner du contenu dans un composable comme suit :

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 **/ }