Oltre al componente componibile Canvas
, Compose ha diverse grafiche utili
Modifiers
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 Crea. In Crea esistono tre modificatori di disegno principali:
Il modificatore di base per il disegno è drawWithContent
, dove puoi decidere l'ordine di disegno del tuo elemento componibile e i comandi di disegno emessi all'interno del modificatore. drawBehind
è un wrapper pratico per drawWithContent
che ha
l'ordine di disegno impostato dietro i contenuti del componente componibile. drawWithCache
chiama onDrawBehind
o onDrawWithContent
al suo interno e fornisce un
meccanismo per memorizzare nella cache gli oggetti creati.
Modifier.drawWithContent
: scegli l'ordine di disegno
Modifier.drawWithContent
ti consente di
eseguire operazioni DrawScope
prima o dopo i contenuti del
componente componibile. Assicurati di chiamare drawContent
per visualizzare i contenuti effettivi
del composable. Con questo modificatore, puoi decidere l'ordine delle operazioni se
vuoi che i contenuti vengano disegnati prima o dopo le operazioni di disegno personalizzato.
Ad esempio, se vuoi eseguire il rendering di un gradiente radiale sopra i tuoi contenuti per creare un effetto di serratura della torcia nell'interfaccia utente, 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 }
Modifier.drawBehind
: Disegno dietro un elemento componibile
Modifier.drawBehind
ti consente di eseguire
operazioni DrawScope
dietro i contenuti componibili visualizzati sullo schermo. Se
dai un'occhiata all'implementazione di Canvas
, potresti notare che
si tratta solo di un wrapper conveniente 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) )
che produce il seguente risultato:

Modifier.drawWithCache
: Disegno e memorizzazione nella cache degli oggetti di disegno
Modifier.drawWithCache
mantiene gli oggetti
creati al suo interno memorizzati nella cache. 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 memorizzare nella cache gli oggetti utilizzando remember
, al di fuori del modificatore. Tuttavia, ciò non è sempre possibile in quanto non sempre hai accesso
alla composizione. L'utilizzo di drawWithCache
può essere più efficiente se gli oggetti vengono utilizzati solo per il disegno.
Ad esempio, se crei un Brush
per disegnare un gradiente dietro un Text
, utilizzando
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()) ) } } )

Modificatori grafici
Modifier.graphicsLayer
: Applica trasformazioni ai composable
Modifier.graphicsLayer
è un modificatore che inserisce il contenuto del composable in un livello di disegno. Un
livello fornisce diverse funzioni, 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 ri-eseguire 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 istruzioni di disegno vengono eseguite e l'output viene acquisito in un buffer off-screen. Il compositing 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 il ridimensionamento o la rotazione.
Trasformazioni
Modifier.graphicsLayer
fornisce l'isolamento per le istruzioni di disegno; ad esempio, è possibile applicare varie trasformazioni utilizzando Modifier.graphicsLayer
.
Questi possono essere animati o modificati senza dover rieseguire la lambda di disegno.
Modifier.graphicsLayer
non modifica le dimensioni o il posizionamento misurati del
componente, in quanto influisce solo sulla fase di disegno. Ciò significa che il tuo elemento componibile
potrebbe sovrapporsi ad altri se viene disegnato al di fuori dei limiti del layout.
Con questo modificatore è possibile applicare le seguenti trasformazioni:
Scala: aumenta le dimensioni
scaleX
e scaleY
ingrandiscono o riducono i contenuti in direzione orizzontale o verticale, rispettivamente. 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 } )
Traduzione
translationX
e translationY
possono essere modificati con graphicsLayer
,
translationX
sposta il componente componibile a sinistra o a destra. translationY
sposta il
componente 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() } )
Rotazione
Imposta rotationX
per la rotazione orizzontale, rotationY
per la rotazione verticale e
rotationZ
per la rotazione 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 } )
Origin
È 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 a (0.5f, 0.5f)
. Se specifichi l'origine in
(0f, 0f)
, le trasformazioni iniziano dall'angolo in alto a sinistra del
componente componibile.
Se modifichi l'origine con una trasformazione rotationZ
, puoi notare che l'elemento ruota attorno all'angolo in alto a sinistra del componente:
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 } )
Clip e forma
Forma specifica il contorno a cui vengono ritagliati i contenuti quando clip = true
. In
questo esempio, abbiamo impostato due caselle in modo che contengano due clip diversi: una utilizza la variabile clip graphicsLayer
e l'altra il wrapper conveniente 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:

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

Per ritagliare il composable nella regione in cui è 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)) ) }

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

Strategia di composizione
Lavorare con alpha e trasparenza potrebbe non essere semplice come modificare un singolo valore alpha. Oltre a modificare un alpha, è possibile anche impostare un
CompositingStrategy
su un graphicsLayer
. Un CompositingStrategy
determina in che modo
il contenuto del composable viene composto (assemblato) con gli altri
contenuti già disegnati sullo schermo.
Le diverse strategie sono:
Automatica (opzione predefinita)
La strategia di composizione è determinata dal resto dei parametri graphicsLayer
. Esegue il rendering del livello in un buffer off-screen se il valore alfa è inferiore a
1.0f o se è impostato un RenderEffect
. Quando l'alpha è inferiore a 1f, viene creato automaticamente un livello di composizione per eseguire il rendering dei contenuti e poi disegnare questo buffer off-screen nella destinazione con l'alpha corrispondente. L'impostazione di un
RenderEffect
o di uno scorrimento eccessivo esegue sempre il rendering dei contenuti in un buffer
fuori schermo, indipendentemente dal valore di CompositingStrategy
impostato.
Fuori schermo
I contenuti del composable vengono sempre rasterizzati in una texture o bitmap
off-screen prima del rendering nella destinazione. Questa funzionalità è utile per
applicare operazioni di BlendMode
per mascherare i contenuti e per migliorare le prestazioni durante
il rendering di set complessi di istruzioni di disegno.
Un esempio di utilizzo di CompositingStrategy.Offscreen
è con BlendModes
. Dando un'occhiata all'esempio riportato di seguito,
supponiamo che tu voglia rimuovere parti di un Image
componibile emettendo un comando di disegno che
utilizza BlendMode.Clear
. Se non imposti compositingStrategy
su
CompositingStrategy.Offscreen
, BlendMode
interagisce con tutti i contenuti
sottostanti.
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 ) ) } } )
Se imposti CompositingStrategy
su Offscreen
, viene creata una texture
off-screen per eseguire i comandi (applicando BlendMode
solo ai
contenuti di questo elemento componibile). e lo esegue sopra ciò che è già
visualizzato sullo schermo, senza influire sui contenuti già disegnati.

Se non hai utilizzato CompositingStrategy.Offscreen
, l'applicazione di
BlendMode.Clear
cancella 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 il canale alfa non funzioneranno come previsto senza un
buffer off-screen. Nota l'anello nero intorno all'indicatore del cerchio rosso:

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

È importante notare che quando si utilizza CompositingStrategy.Offscreen
, viene creata e visualizzata sullo schermo una texture
fuori schermo delle dimensioni dell'area di disegno. Per impostazione predefinita, tutti i comandi di disegno eseguiti con questa strategia vengono
ritagliati in questa regione. Lo snippet di codice riportato di seguito illustra le differenze quando
si passa all'utilizzo di texture off-screen:
@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())) } } }

ModulateAlpha
Questa strategia di composizione modula l'alpha per ciascuna delle istruzioni di disegno registrate all'interno di graphicsLayer
. Non creerà un buffer
off-screen per l'alpha inferiore a 1.0f a meno che non sia impostato un RenderEffect
, quindi può
essere più efficiente per il rendering alpha. 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, questa opzione può offrire un rendimento migliore rispetto a
CompositingStrategy.Auto
con valori alfa inferiori a 1.
Di seguito è riportato un altro esempio di diverse strategie di composizione: l'applicazione di valori alfa diversi a parti diverse dei componenti 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) ) } val Purple = Color(0xFF7E57C2) val Yellow = Color(0xFFFFCA28) val Red = Color(0xFFEF5350)

Scrivere i contenuti di un elemento componibile in una bitmap
Un caso d'uso comune è la creazione di un Bitmap
da un composable. Per copiare i contenuti del tuo 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 su disco e condividerla. Per maggiori dettagli, consulta lo snippet di esempio completo. Assicurati di controllare le autorizzazioni sul dispositivo prima di provare a salvare sul disco.
Modificatore di disegno personalizzato
Per creare un modificatore personalizzato, implementa l'interfaccia DrawModifier
. In questo modo, hai accesso a un ContentDrawScope
, che è lo stesso di quello esposto quando utilizzi Modifier.drawWithContent()
. Puoi quindi estrarre operazioni di disegno comuni in modificatori di disegno personalizzati per pulire il codice e fornire wrapper pratici; ad esempio, Modifier.background()
è un DrawModifier
pratico.
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() )

Risorse aggiuntive
Per altri esempi di utilizzo di graphicsLayer
e del disegno personalizzato, consulta le
seguenti risorse:
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Grafica in Composizione
- Personalizzare un'immagine {:#customize-image}
- Kotlin per Jetpack Compose