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

ב-Compose, אובייקט Painter משמש לייצוג של משהו שאפשר לצייר (תחליף לממשקי ה-API של Drawable שמוגדרים ב-Android) ומשפיע על המדידה ועל הפריסה של הרכיב המתאים שאפשר להרכיב שמשתמש בו . ‫A 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: Custom Painter שיוצר שכבת-על של שתי תמונות אחת על השנייה

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

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