צבעים בהתאמה אישית

ב-Compose, אובייקט Painter משמש לייצוג משהו שאפשר לצייר (החלפה לממשקי ה-API של Drawable שהוגדרו ב-Android) ולהשפיע על המדידה והפריסה של ה-composable התואם שמשתמש בו . BitmapPainter מקבל ImageBitmap שיכול לצייר Bitmap במסך.

ברוב תרחישי השימוש, השימוש ב-painterResource() שלמעלה מחזיר את ה-painter הנכון לנכס (כלומר BitmapPainter או VectorPainter). למידע נוסף על ההבדלים בין השניים, קראו את הקטע ImageBitmap לעומת ImageVector.

Painter שונה מ-DrawModifier, שמצויר בגבולות שניתנים לו ולא משפיע על המדידה או על הפריסה של הרכיב הניתן לקיבוץ.

כדי ליצור צייר מותאם אישית, צריך להרחיב את הכיתה Painter ולהטמיע את השיטה onDraw, שמאפשרת גישה ל-DrawScope כדי לצייר גרפיקה בהתאמה אישית. אפשר גם לשנות את הערך של intrinsicSize, שישפיע על ה-Composable שבו הוא נכלל:

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

עכשיו, אחרי שיצרנו את Painter בהתאמה אישית, אנחנו יכולים להוסיף שכבת-על של כל תמונה מעל לתמונה המקורית באופן הבא:

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

התוצאה של שילוב שתי התמונות באמצעות כלי ציור מותאם אישית מוצגת בהמשך:

כלי ציור בהתאמה אישית שמאפשר להציב שכבת-על של שתי תמונות אחת על גבי השנייה
איור 1: כלי ציור בהתאמה אישית שמציב שכבת-על של שתי תמונות אחת על השנייה

אפשר גם להשתמש בצייר מותאם אישית עם Modifier.paint(customPainter) כדי לצייר את התוכן ב-Composable באופן הבא:

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