Modificatori grafici

Oltre al componibile Canvas, Compose include diversi Modifiers grafici utili che aiutano a disegnare contenuti personalizzati. Questi modificatori sono utili perché possono essere applicati a qualsiasi componibile.

Modificatori di disegno

Tutti i comandi di disegno vengono eseguiti con un modificatore di disegno in Compose. In Compose sono presenti tre modificatori di disegno principali:

Il modificatore di base per il disegno è drawWithContent, con cui puoi decidere l'ordine di disegno del tuo componibile e i comandi di disegno emessi all'interno del modificatore. drawBehind è un comodo wrapper di drawWithContent con l'ordine di disegno impostato dietro i contenuti del composable. drawWithCache chiama onDrawBehind o onDrawWithContent al suo interno e fornisce un meccanismo per memorizzare nella cache gli oggetti creati al loro interno.

Modifier.drawWithContent: scegli l'ordine di disegno

Modifier.drawWithContent ti consente di eseguire operazioni DrawScope prima o dopo i contenuti del componibile. Assicurati di chiamare drawContent per eseguire il rendering dei contenuti effettivi del composable. Con questo modificatore, puoi decidere l'ordine delle operazioni, se vuoi che i tuoi contenuti vengano disegnati prima o dopo le operazioni di disegno personalizzate.

Ad esempio, se vuoi eseguire il rendering di una sfumatura radiale sopra i tuoi contenuti per creare un effetto di luce di una torcia sulla UI, puoi procedere nel seguente modo:

var pointerOffset by remember {
    mutableStateOf(Offset(0f, 0f))
}
Column(
    modifier = Modifier
        .fillMaxSize()
        .pointerInput("dragging") {
            detectDragGestures { change, dragAmount ->
                pointerOffset += dragAmount
            }
        }
        .onSizeChanged {
            pointerOffset = Offset(it.width / 2f, it.height / 2f)
        }
        .drawWithContent {
            drawContent()
            // draws a fully black area with a small keyhole at pointerOffset that’ll show part of the UI.
            drawRect(
                Brush.radialGradient(
                    listOf(Color.Transparent, Color.Black),
                    center = pointerOffset,
                    radius = 100.dp.toPx(),
                )
            )
        }
) {
    // Your composables here
}

Figura 1: Modifier.drawWithContent utilizzato sopra un componibile per creare un'esperienza UI di tipo torcia.

Modifier.drawBehind: disegno dietro un composable

Modifier.drawBehind ti consente di eseguire operazioni DrawScope dietro i contenuti componibili disegnati sullo schermo. Se esamini l'implementazione di Canvas, potresti notare che si tratta solo di un comodo wrapper per Modifier.drawBehind.

Per disegnare un rettangolo arrotondato dietro Text:

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawBehind {
            drawRoundRect(
                Color(0xFFBBAAEE),
                cornerRadius = CornerRadius(10.dp.toPx())
            )
        }
        .padding(4.dp)
)

Il risultato è il seguente:

Testo e sfondo disegnati utilizzando Modifier.drawBehind
Figura 2: testo e sfondo disegnati utilizzando Modifier.drawBehind

Modifier.drawWithCache: disegno e memorizzazione nella cache degli oggetti di disegno

Modifier.drawWithCache mantiene memorizzati nella cache gli oggetti creati al suo interno. Gli oggetti vengono memorizzati nella cache finché le dimensioni dell'area di disegno rimangono invariate o finché gli oggetti di stato letti non cambiano. Questo modificatore è utile per migliorare le prestazioni delle chiamate di disegno, in quanto evita la necessità di riallocare gli oggetti (ad esempio Brush, Shader, Path e così via) creati durante il disegno.

In alternativa, puoi anche memorizzare nella cache gli oggetti utilizzando remember, al di fuori del modificatore. Tuttavia, questo non è sempre possibile, in quanto non sempre hai accesso alla composizione. Può essere più efficiente utilizzare drawWithCache se gli oggetti vengono utilizzati solo per il disegno.

Ad esempio, se crei un Brush per disegnare un gradiente dietro un Text, l'utilizzo di drawWithCache memorizza nella cache l'oggetto Brush finché le dimensioni dell'area di disegno non cambiano:

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawWithCache {
            val brush = Brush.linearGradient(
                listOf(
                    Color(0xFF9E82F0),
                    Color(0xFF42A5F5)
                )
            )
            onDrawBehind {
                drawRoundRect(
                    brush,
                    cornerRadius = CornerRadius(10.dp.toPx())
                )
            }
        }
)

Memorizzazione nella cache dell'oggetto Brush con drawWithCache
Figura 3: memorizzazione nella cache dell'oggetto Brush con drawWithCache

Modificatori grafici

Modifier.graphicsLayer: applica le trasformazioni ai componibili

Modifier.graphicsLayer è un modificatore che fa sì che i contenuti del composable vengano disegnati in un livello di disegno. Un livello fornisce alcune funzioni diverse, ad esempio:

  • Isolamento per le istruzioni di disegno (simile a RenderNode). Le istruzioni di disegno acquisite come parte di un livello possono essere riemesse in modo efficiente dalla pipeline di rendering senza rieseguire il codice dell'applicazione.
  • Trasformazioni che si applicano a tutte le istruzioni di disegno contenute in un livello.
  • Rasterizzazione per le funzionalità di composizione. Quando un livello viene rasterizzato, le relative istruzioni di disegno vengono eseguite e l'output viene acquisito in un buffer fuori schermo. La composizione di un buffer di questo tipo per i frame successivi è più veloce dell'esecuzione delle singole istruzioni, ma si comporterà come una bitmap quando vengono applicate trasformazioni come la scalabilità o la rotazione.

Trasformazioni

Modifier.graphicsLayer fornisce l'isolamento per le sue istruzioni di disegno; ad esempio, è possibile applicare varie trasformazioni utilizzando Modifier.graphicsLayer. Questi possono essere animati o modificati senza dover eseguire nuovamente la funzione lambda di disegno.

Modifier.graphicsLayer non modifica le dimensioni o la posizione misurate del composable, poiché influisce solo sulla fase di disegno. Ciò significa che il tuo composable potrebbe sovrapporsi ad altri se finisce per disegnare al di fuori dei limiti del layout.

Con questo modificatore è possibile applicare le seguenti trasformazioni:

Scala: aumenta le dimensioni

scaleX e scaleY ingrandiscono o rimpiccioliscono i contenuti rispettivamente in direzione orizzontale o verticale. Un valore pari a 1.0f indica che non è stata apportata alcuna modifica alla scala, mentre un valore pari a 0.5f indica la metà della dimensione.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.scaleX = 1.2f
            this.scaleY = 0.8f
        }
)

Figura 4: scaleX e scaleY applicati a un elemento componibile Image
Traduzione

translationX e translationY possono essere modificati con graphicsLayer, translationX sposta il composable a sinistra o a destra. translationY sposta il composable verso l'alto o verso il basso.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.translationX = 100.dp.toPx()
            this.translationY = 10.dp.toPx()
        }
)

Figura 5: translationX e translationY applicati all'immagine con Modifier.graphicsLayer
Rotazione

Imposta rotationX per ruotare in orizzontale, rotationY per ruotare in verticale e rotationZ per ruotare sull'asse Z (rotazione standard). Questo valore è specificato in gradi (0-360).

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

Figura 6: rotationX, rotationY e rotationZ impostati su Image da Modifier.graphicsLayer
Origine

È possibile specificare un transformOrigin. Viene quindi utilizzato come punto da cui avvengono le trasformazioni. Tutti gli esempi finora hanno utilizzato TransformOrigin.Center, che si trova in (0.5f, 0.5f). Se specifichi l'origine in (0f, 0f), le trasformazioni iniziano dall'angolo in alto a sinistra del composable.

Se modifichi l'origine con una trasformazione rotationZ, puoi vedere che l'elemento ruota attorno all'angolo in alto a sinistra del composable:

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.transformOrigin = TransformOrigin(0f, 0f)
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

Figura 7: rotazione applicata con TransformOrigin impostato su 0f, 0f

Clip and Shape

La forma specifica il contorno a cui i contenuti vengono ritagliati quando clip = true. In questo esempio, impostiamo due riquadri in modo che abbiano due clip diversi: uno utilizza la variabile clip graphicsLayer e l'altro utilizza il comodo wrapper Modifier.clip.

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .size(200.dp)
            .graphicsLayer {
                clip = true
                shape = CircleShape
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }
    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(CircleShape)
            .background(Color(0xFF4DB6AC))
    )
}

I contenuti della prima casella (il testo "Hello Compose") vengono ritagliati in base alla forma del cerchio:

Clip applicato al composable Box
Figura 8: clip applicato al composable Box

Se poi applichi un translationY al cerchio rosa in alto, vedrai che i limiti del componibile sono ancora gli stessi, ma il cerchio viene disegnato sotto il cerchio in basso (e al di fuori dei suoi limiti).

Clip applicato con translationY e bordo rosso per il contorno
Figura 9: clip applicato con translationY e bordo rosso per il contorno

Per ritagliare il composable nell'area in cui viene disegnato, puoi aggiungere un altro Modifier.clip(RectangleShape) all'inizio della catena di modificatori. I contenuti rimangono quindi all'interno dei limiti originali.

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .clip(RectangleShape)
            .size(200.dp)
            .border(2.dp, Color.Black)
            .graphicsLayer {
                clip = true
                shape = CircleShape
                translationY = 50.dp.toPx()
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }

    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(RoundedCornerShape(500.dp))
            .background(Color(0xFF4DB6AC))
    )
}

Clip applicato sopra la trasformazione graphicsLayer
Figura 10: ritaglio applicato sopra la trasformazione graphicsLayer

Alpha

Modifier.graphicsLayer può essere utilizzato per impostare un alpha (opacità) per l'intero livello. 1.0f è completamente opaco e 0.0f è invisibile.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "clock",
    modifier = Modifier
        .graphicsLayer {
            this.alpha = 0.5f
        }
)

Immagine con canale alfa applicato
Figura 11: immagine con alpha applicato

Strategia di compositing

Lavorare con l'alpha e la trasparenza non è sempre così semplice come modificare un singolo valore alpha. Oltre a modificare un valore alfa, è anche possibile impostare un CompositingStrategy su un graphicsLayer. Un CompositingStrategy determina il modo in cui i contenuti del composable vengono composti (messi insieme) con gli altri contenuti già disegnati sullo schermo.

Le diverse strategie sono:

Automatica (opzione predefinita)

La strategia di compositing è determinata dal resto dei parametri graphicsLayer. Esegue il rendering del livello in un buffer fuori schermo se l'alpha è inferiore a 1.0f o se è impostato un RenderEffect. Ogni volta che l'alpha è inferiore a 1f, viene creato automaticamente un livello di compositing per eseguire il rendering dei contenuti e poi disegnare questo buffer fuori schermo nella destinazione con l'alpha corrispondente. L'impostazione di un valore RenderEffect o di overscroll esegue sempre il rendering dei contenuti in un buffer fuori schermo, indipendentemente dal valore CompositingStrategy impostato.

Fuori schermo

I contenuti del composable vengono sempre rasterizzati in una texture o bitmap fuori schermo prima del rendering nella destinazione. Questo è utile per applicare operazioni BlendMode per mascherare i contenuti e per le prestazioni durante il rendering di insiemi complessi di istruzioni di disegno.

Un esempio di utilizzo di CompositingStrategy.Offscreen è con BlendModes. Nell'esempio seguente, supponiamo che tu voglia rimuovere parti di un componibile Image eseguendo un comando di disegno che utilizza BlendMode.Clear. Se non imposti compositingStrategy su CompositingStrategy.Offscreen, BlendMode interagisce con tutti i contenuti in background.

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = "Dog",
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(120.dp)
        .aspectRatio(1f)
        .background(
            Brush.linearGradient(
                listOf(
                    Color(0xFFC5E1A5),
                    Color(0xFF80DEEA)
                )
            )
        )
        .padding(8.dp)
        .graphicsLayer {
            compositingStrategy = CompositingStrategy.Offscreen
        }
        .drawWithCache {
            val path = Path()
            path.addOval(
                Rect(
                    topLeft = Offset.Zero,
                    bottomRight = Offset(size.width, size.height)
                )
            )
            onDrawWithContent {
                clipPath(path) {
                    // this draws the actual image - if you don't call drawContent, it wont
                    // render anything
                    this@onDrawWithContent.drawContent()
                }
                val dotSize = size.width / 8f
                // Clip a white border for the content
                drawCircle(
                    Color.Black,
                    radius = dotSize,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    ),
                    blendMode = BlendMode.Clear
                )
                // draw the red circle indication
                drawCircle(
                    Color(0xFFEF5350), radius = dotSize * 0.8f,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    )
                )
            }
        }
)

Impostando CompositingStrategy su Offscreen, viene creata una texture fuori schermo per eseguire i comandi (applicando BlendMode solo ai contenuti di questo composable). Quindi, lo visualizza sopra ciò che è già visualizzato sullo schermo, senza influire sui contenuti già disegnati.

Modifier.drawWithContent su un'immagine che mostra un'indicazione circolare, con BlendMode.Clear all'interno dell'app
Figura 12: Modifier.drawWithContent su un'immagine che mostra un'indicazione circolare, con BlendMode.Clear e CompositingStrategy.Offscreen all'interno dell'app

Se non hai utilizzato CompositingStrategy.Offscreen, i risultati dell'applicazione di BlendMode.Clear cancellano tutti i pixel nella destinazione, indipendentemente da ciò che era già impostato, lasciando visibile il buffer di rendering della finestra (nero). Molte delle BlendModes che coinvolgono l'alpha non funzioneranno come previsto senza un buffer fuori schermo. Nota l'anello nero intorno all'indicatore circolare rosso:

Modifier.drawWithContent su un'immagine che mostra un'indicazione circolare, con BlendMode.Clear e nessuna CompositingStrategy impostata
Figura 13: Modifier.drawWithContent su un'immagine che mostra un'indicazione circolare, con BlendMode.Clear e nessuna CompositingStrategy impostata

Per capire meglio: se l'app aveva uno sfondo della finestra traslucido e non hai utilizzato CompositingStrategy.Offscreen, BlendMode interagiva con l'intera app. Cancellava tutti i pixel per mostrare l'app o lo sfondo sottostante, come in questo esempio:

Nessuna CompositingStrategy impostata e utilizzo di BlendMode.Clear con un'app che ha uno sfondo della finestra traslucido. Lo sfondo rosa è visibile nell'area intorno al cerchio di stato rosso.
Figura 14: nessuna strategia di composizione impostata e utilizzo di BlendMode.Clear con un'app che ha uno sfondo della finestra traslucido. Nota come lo sfondo rosa viene mostrato attraverso l'area intorno al cerchio rosso dello stato.

Vale la pena notare che, quando utilizzi CompositingStrategy.Offscreen, viene creata una texture fuori schermo delle dimensioni dell'area di disegno, che viene poi visualizzata di nuovo sullo schermo. Tutti i comandi di disegno eseguiti con questa strategia vengono ritagliati per impostazione predefinita in questa regione. Il seguente snippet di codice illustra le differenze quando si passa all'utilizzo di texture fuori schermo:

@Composable
fun CompositingStrategyExamples() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize(Alignment.Center)
    ) {
        // Does not clip content even with a graphics layer usage here. By default, graphicsLayer
        // does not allocate + rasterize content into a separate layer but instead is used
        // for isolation. That is draw invalidations made outside of this graphicsLayer will not
        // re-record the drawing instructions in this composable as they have not changed
        Canvas(
            modifier = Modifier
                .graphicsLayer()
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            // ... and drawing a size of 200 dp here outside the bounds
            drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }

        Spacer(modifier = Modifier.size(300.dp))

        /* Clips content as alpha usage here creates an offscreen buffer to rasterize content
        into first then draws to the original destination */
        Canvas(
            modifier = Modifier
                // force to an offscreen buffer
                .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            /* ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the
            content gets clipped */
            drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }
    }
}

CompositingStrategy.Auto rispetto a CompositingStrategy.Offscreen: offscreen ritaglia l'area, mentre auto no
Figura 15: CompositingStrategy.Auto rispetto a CompositingStrategy.Offscreen - offscreen ritaglia l'area, mentre auto no
ModulateAlpha

Questa strategia di composizione modula l'alpha per ciascuna delle istruzioni di disegno registrate all'interno di graphicsLayer. Non creerà un buffer fuori schermo per un valore alfa inferiore a 1,0f, a meno che non sia impostato un RenderEffect, quindi può essere più efficiente per il rendering alfa. Tuttavia, può fornire risultati diversi per i contenuti sovrapposti. Per i casi d'uso in cui è noto in anticipo che i contenuti non si sovrappongono, questo può fornire prestazioni migliori rispetto a CompositingStrategy.Auto con valori alfa inferiori a 1.

L'esempio seguente mostra diverse strategie di composizione: l'applicazione di diversi valori alfa a diverse parti dei componibili e l'applicazione di una strategia Modulate:

@Preview
@Composable
fun CompositingStrategy_ModulateAlpha() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(32.dp)
    ) {
        // Base drawing, no alpha applied
        Canvas(
            modifier = Modifier.size(200.dp)
        ) {
            drawSquares()
        }

        Spacer(modifier = Modifier.size(36.dp))

        // Alpha 0.5f applied to whole composable
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    alpha = 0.5f
                }
        ) {
            drawSquares()
        }
        Spacer(modifier = Modifier.size(36.dp))

        // 0.75f alpha applied to each draw call when using ModulateAlpha
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    compositingStrategy = CompositingStrategy.ModulateAlpha
                    alpha = 0.75f
                }
        ) {
            drawSquares()
        }
    }
}

private fun DrawScope.drawSquares() {

    val size = Size(100.dp.toPx(), 100.dp.toPx())
    drawRect(color = Red, size = size)
    drawRect(
        color = Purple, size = size,
        topLeft = Offset(size.width / 4f, size.height / 4f)
    )
    drawRect(
        color = Yellow, size = size,
        topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f)
    )
}

@Preview
@Composable
fun ModifierGraphicsLayerOutsets() {
    Column(
        modifier = Modifier
            .background(Color(0xFF0A0E14))
            .padding(48.dp),
        verticalArrangement = Arrangement.spacedBy(48.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        val pillShape = RoundedCornerShape(percent = 50)

        // Without outsets: alpha = 0.5f creates an offscreen buffer that clips the 24.dp glow
        Box(
            modifier = Modifier
                .size(
                    width = 140.dp,
                    height = 56.dp
                )
                .graphicsLayer(
                    alpha = 0.5f,
                    clip = false
                )
                .dropShadow(
                    shape = pillShape,
                    shadow = Shadow(radius = 24.dp, color = Color.Cyan)
                )
                .background(color = Color(0xFF141824), shape = pillShape),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "✨ Glow",
                color = Color.White,
                fontSize = 18.sp
            )
        }

        Box(
            modifier = Modifier
                .size(
                    width = 140.dp,
                    height = 56.dp
                )
                .graphicsLayer(
                    alpha = 0.5f,
                    outsets = LayerOutsets(24.dp),
                    clip = false
                )
                .dropShadow(
                    shape = pillShape,
                    shadow = Shadow(radius = 24.dp, color = Color.Cyan)
                )
                .background(color = Color(0xFF141824), shape = pillShape),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "✨ Glow",
                color = Color.White,
                fontSize = 18.sp
            )
        }
    }
}


val Purple = Color(0xFF7E57C2)
val Yellow = Color(0xFFFFCA28)
val Red = Color(0xFFEF5350)

ModulateAlpha applica l'alpha impostato a ogni singolo comando di disegno
Figura 16: ModulateAlpha applica l'alpha impostato a ogni singolo comando di disegno

Margini esterni dei livelli

Quando un composable viene disegnato in un buffer fuori schermo (ad esempio, quando alpha è inferiore a 1.0f, viene impostato un RenderEffect o viene utilizzato CompositingStrategy.Offscreen), la texture fuori schermo viene impostata per impostazione predefinita sui limiti del layout misurato del composable. Qualsiasi disegno o ombra all'interno del livello che si estende oltre i limiti del layout del composable (ad esempio un bagliore esterno disegnato con Modifier.dropShadow) viene ritagliato implicitamente al confine del buffer fuori schermo.

Anziché aggiungere Modifier.padding() (che sposta la misurazione del layout degli elementi UI adiacenti), passa LayerOutsets al parametro outsets di Modifier.graphicsLayer. In questo modo, i limiti visivi del buffer fuori schermo vengono espansi su ogni lato senza alterare le dimensioni del layout misurate del composable. Tieni presente che LayerOutsets non espande la forma di ritaglio o i limiti dell'ombra del livello, che rimangono basati sulle dimensioni misurate originali del componibile (quindi mantieni clip = false su graphicsLayer per evitare il ritaglio ai limiti originali):

Box(
    modifier = Modifier
        .size(
            width = 140.dp,
            height = 56.dp
        )
        .graphicsLayer(
            alpha = 0.5f,
            outsets = LayerOutsets(24.dp),
            clip = false
        )
        .dropShadow(
            shape = pillShape,
            shadow = Shadow(radius = 24.dp, color = Color.Cyan)
        )
        .background(color = Color(0xFF141824), shape = pillShape),
    contentAlignment = Alignment.Center
) {
    Text(
        text = "✨ Glow",
        color = Color.White,
        fontSize = 18.sp
    )
}

Confronto tra un alone esterno di un livello fuori schermo senza LayerOutsets (in alto, ritagliato in base ai limiti del layout rettangolare) e con LayerOutsets(24.dp) (in basso, non ritagliato)
Figura 17: alone esterno del livello fuori schermo senza LayerOutsets (in alto, ritagliato in base ai limiti del layout rettangolare del composable) e con LayerOutsets(24.dp) (in basso, non ritagliato)

Scrivi i contenuti di un composable in una bitmap

Un caso d'uso comune è la creazione di un Bitmap da un componibile. Per copiare i contenuti del componibile in un Bitmap, crea un GraphicsLayer utilizzando rememberGraphicsLayer().

Reindirizza i comandi di disegno al nuovo livello utilizzando drawWithContent() e graphicsLayer.record{}. Poi disegna il livello nel canvas visibile utilizzando drawLayer:

val coroutineScope = rememberCoroutineScope()
val graphicsLayer = rememberGraphicsLayer()
Box(
    modifier = Modifier
        .drawWithContent {
            // call record to capture the content in the graphics layer
            graphicsLayer.record {
                // draw the contents of the composable into the graphics layer
                this@drawWithContent.drawContent()
            }
            // draw the graphics layer on the visible canvas
            drawLayer(graphicsLayer)
        }
        .clickable {
            coroutineScope.launch {
                val bitmap = graphicsLayer.toImageBitmap()
                // do something with the newly acquired bitmap
            }
        }
        .background(Color.White)
) {
    Text("Hello Android", fontSize = 26.sp)
}

Puoi salvare la bitmap sul disco e condividerla. Per maggiori dettagli, consulta lo snippet di esempio completo. Assicurati di controllare le autorizzazioni sul dispositivo prima di provare a salvare su disco.

Modificatore di disegno personalizzato

Per creare un modificatore personalizzato, implementa l'interfaccia DrawModifier. In questo modo, puoi accedere a un ContentDrawScope, che è lo stesso esposto quando utilizzi Modifier.drawWithContent(). Puoi quindi estrarre le operazioni di disegno comuni in modificatori di disegno personalizzati per ripulire il codice e fornire wrapper pratici; ad esempio, Modifier.background() è un pratico DrawModifier.

Ad esempio, se vuoi implementare un Modifier che capovolge verticalmente i contenuti, puoi crearne uno come segue:

class FlippedModifier : DrawModifier {
    override fun ContentDrawScope.draw() {
        scale(1f, -1f) {
            this@draw.drawContent()
        }
    }
}

fun Modifier.flipped() = this.then(FlippedModifier())

Quindi utilizza questo modificatore invertito applicato a Text:

Text(
    "Hello Compose!",
    modifier = Modifier
        .flipped()
)

Modificatore capovolto personalizzato sul testo
Figura 17: modificatore personalizzato Flipped sul testo

Risorse aggiuntive

Per altri esempi di utilizzo di graphicsLayer e disegno personalizzato, consulta le seguenti risorse: