Zusätzlich zur zusammensetzbaren Funktion Canvas
bietet Compose mehrere nützliche Modifiers
für Grafiken, die beim Zeichnen benutzerdefinierter Inhalte helfen. Diese Modifikatoren sind nützlich, da sie auf jedes Composable angewendet werden können.
Zeichnungsmodifikatoren
Alle Zeichenbefehle werden mit einem Zeichenmodifikator in Compose ausgeführt. Es gibt drei Hauptmodifikatoren für das Zeichnen in Compose:
Der Basismodifikator für das Zeichnen ist drawWithContent
. Damit können Sie die Zeichenreihenfolge Ihrer Composable und die Zeichenbefehle festlegen, die im Modifikator ausgegeben werden. drawBehind
ist ein praktischer Wrapper um drawWithContent
, bei dem die Zeichenreihenfolge auf „hinter dem Inhalt des Composables“ festgelegt ist. drawWithCache
ruft entweder onDrawBehind
oder onDrawWithContent
auf und bietet einen Mechanismus zum Zwischenspeichern der darin erstellten Objekte.
Modifier.drawWithContent
: Zeichenreihenfolge auswählen
Mit Modifier.drawWithContent
können Sie DrawScope
-Vorgänge vor oder nach dem Inhalt des Composables ausführen. Rufen Sie drawContent
auf, um den tatsächlichen Inhalt des Composables zu rendern. Mit diesem Modifikator können Sie die Reihenfolge der Vorgänge festlegen, wenn Ihre Inhalte vor oder nach Ihren benutzerdefinierten Zeichenvorgängen gezeichnet werden sollen.
Wenn Sie beispielsweise einen radialen Farbverlauf über Ihren Inhalten rendern möchten, um einen Schlüsselloch-Effekt auf der Benutzeroberfläche zu erzeugen, können Sie Folgendes tun:
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
: Zeichnung hinter einem Composable
Mit Modifier.drawBehind
können Sie DrawScope
-Vorgänge hinter den Composable-Inhalten ausführen, die auf dem Bildschirm dargestellt werden. Wenn Sie sich die Implementierung von Canvas
ansehen, werden Sie feststellen, dass es sich nur um einen praktischen Wrapper für Modifier.drawBehind
handelt.
So zeichnen Sie ein abgerundetes Rechteck hinter Text
:
Text( "Hello Compose!", modifier = Modifier .drawBehind { drawRoundRect( Color(0xFFBBAAEE), cornerRadius = CornerRadius(10.dp.toPx()) ) } .padding(4.dp) )
Das führt zu folgendem Ergebnis:

Modifier.drawWithCache
: Ziehungsobjekte zeichnen und im Cache speichern
Mit Modifier.drawWithCache
werden die darin erstellten Objekte im Cache gespeichert. Die Objekte werden so lange im Cache gespeichert, wie die Größe des Zeichenbereichs gleich ist oder sich keine der gelesenen Statusobjekte geändert hat. Dieser Modifikator ist nützlich, um die Leistung von Zeichenaufrufen zu verbessern, da er die Notwendigkeit vermeidet, Objekte (z. B. Brush, Shader, Path
usw.) neu zuzuweisen, die beim Zeichnen erstellt werden.
Alternativ können Sie Objekte auch mit remember
außerhalb des Modifikators im Cache speichern. Das ist jedoch nicht immer möglich, da Sie nicht immer Zugriff auf die Komposition haben. Die Verwendung von drawWithCache
kann leistungsfähiger sein, wenn die Objekte nur zum Zeichnen verwendet werden.
Wenn Sie beispielsweise ein Brush
erstellen, um einen Farbverlauf hinter einem Text
zu zeichnen, wird das Brush
-Objekt mit drawWithCache
zwischengespeichert, bis sich die Größe des Zeichenbereichs ändert:
Text( "Hello Compose!", modifier = Modifier .drawWithCache { val brush = Brush.linearGradient( listOf( Color(0xFF9E82F0), Color(0xFF42A5F5) ) ) onDrawBehind { drawRoundRect( brush, cornerRadius = CornerRadius(10.dp.toPx()) ) } } )

Grafikmodifikatoren
Modifier.graphicsLayer
: Transformationen auf Composables anwenden
Modifier.graphicsLayer
ist ein Modifikator, mit dem der Inhalt der Composable in eine Zeichenebene gezeichnet wird. Eine Ebene bietet verschiedene Funktionen, z. B.:
- Isolierung für die Zeichenanweisungen (ähnlich wie bei
RenderNode
): Zeichenanweisungen, die als Teil einer Ebene erfasst werden, können von der Rendering-Pipeline effizient neu ausgegeben werden, ohne dass Anwendungscode neu ausgeführt werden muss. - Transformationen, die auf alle Zeichenanweisungen in einer Ebene angewendet werden.
- Rasterung für Kompositionsfunktionen. Wenn eine Ebene gerastert wird, werden ihre Zeichenanweisungen ausgeführt und die Ausgabe wird in einem Offscreen-Puffer erfasst. Das Zusammensetzen eines solchen Puffers für nachfolgende Frames ist schneller als die Ausführung der einzelnen Anweisungen. Bei Transformationen wie Skalierung oder Drehung verhält er sich jedoch wie eine Bitmap.
Transformationen
Modifier.graphicsLayer
bietet eine Isolation für die zugehörigen Zeichenanweisungen. So können beispielsweise verschiedene Transformationen mit Modifier.graphicsLayer
angewendet werden.
Sie können animiert oder geändert werden, ohne dass das Lambda für das Zeichnen noch einmal ausgeführt werden muss.
Modifier.graphicsLayer
ändert die gemessene Größe oder Position Ihres Composables nicht, da es sich nur auf die Zeichenphase auswirkt. Das bedeutet, dass sich Ihre zusammensetzbare Funktion möglicherweise mit anderen überschneidet, wenn sie außerhalb ihrer Layoutgrenzen gerendert wird.
Mit diesem Modifikator können die folgenden Transformationen angewendet werden:
Skalieren – Größe erhöhen
Mit scaleX
und scaleY
wird der Inhalt in horizontaler bzw. vertikaler Richtung vergrößert oder verkleinert. Ein Wert von 1.0f
bedeutet keine Änderung des Maßstabs, ein Wert von 0.5f
die Hälfte der Dimension.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.scaleX = 1.2f this.scaleY = 0.8f } )
Übersetzung
translationX
und translationY
können mit graphicsLayer
geändert werden.
translationX
verschiebt das Composable nach links oder rechts. Mit translationY
wird das Composable nach oben oder unten verschoben.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.translationX = 100.dp.toPx() this.translationY = 10.dp.toPx() } )
Ausrichtung
Legen Sie rotationX
für die horizontale Drehung, rotationY
für die vertikale Drehung und rotationZ
für die Drehung auf der Z-Achse (Standarddrehung) fest. Dieser Wert wird in Grad (0–360) angegeben.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
Origin
Ein transformOrigin
kann angegeben werden. Er wird dann als Ausgangspunkt für Transformationen verwendet. In allen bisherigen Beispielen wurde TransformOrigin.Center
verwendet, das sich unter (0.5f, 0.5f)
befindet. Wenn Sie den Ursprung bei (0f, 0f)
angeben, beginnen die Transformationen in der linken oberen Ecke der Composable.
Wenn Sie den Ursprung mit einer rotationZ
-Transformation ändern, dreht sich das Element um die obere linke Ecke des Composables:
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 und Form
Mit „Form“ wird der Umriss angegeben, an den der Inhalt angelegt wird, wenn clip = true
. In diesem Beispiel legen wir für zwei Kästen zwei verschiedene Clips fest – einen mit der Clip-Variablen graphicsLayer
und den anderen mit dem praktischen 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)) ) }
Der Inhalt des ersten Felds (der Text „Hello Compose“) wird auf die Kreisform zugeschnitten:

Wenn Sie dann ein translationY
auf den oberen rosafarbenen Kreis anwenden, sehen Sie, dass die Grenzen der Composable weiterhin gleich sind, der Kreis jedoch unter dem unteren Kreis (und außerhalb seiner Grenzen) gezeichnet wird.

Wenn Sie das Composable auf die Region beschränken möchten, in der es gezeichnet wird, können Sie am Anfang der Modifier-Kette ein weiteres Modifier.clip(RectangleShape)
hinzufügen. Die Inhalte bleiben dann innerhalb der ursprünglichen Grenzen.
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
Mit Modifier.graphicsLayer
kann eine alpha
(Deckkraft) für die gesamte Ebene festgelegt werden. 1.0f
ist vollständig undurchsichtig und 0.0f
ist unsichtbar.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "clock", modifier = Modifier .graphicsLayer { this.alpha = 0.5f } )

Compositing-Strategie
Das Arbeiten mit Alpha und Transparenz ist möglicherweise nicht so einfach wie das Ändern eines einzelnen Alphawerts. Neben der Änderung eines Alpha-Werts besteht auch die Möglichkeit, einen CompositingStrategy
für einen graphicsLayer
festzulegen. Eine CompositingStrategy
bestimmt, wie der Inhalt der Composable mit den anderen Inhalten, die bereits auf dem Bildschirm gezeichnet wurden, zusammengesetzt wird.
Folgende Strategien sind verfügbar:
Automatisch (Standard)
Die Compositing-Strategie wird durch die restlichen graphicsLayer
-Parameter bestimmt. Die Ebene wird in einem Offscreen-Puffer gerendert, wenn der Alphawert kleiner als 1,0 f ist oder ein RenderEffect
festgelegt ist. Wenn der Alphawert kleiner als 1f ist, wird automatisch eine Compositing-Ebene erstellt, um den Inhalt zu rendern und diesen Offscreen-Puffer dann mit dem entsprechenden Alphawert auf das Ziel zu zeichnen. Wenn Sie RenderEffect
oder Overscroll festlegen, werden Inhalte immer in einem Offscreen-Puffer gerendert, unabhängig von der Einstellung für CompositingStrategy
.
Nicht sichtbar
Der Inhalt des Composables wird immer in eine Offscreen-Textur oder -Bitmap gerastert, bevor er gerendert wird. Das ist nützlich, um BlendMode
-Vorgänge zum Maskieren von Inhalten anzuwenden und die Leistung beim Rendern komplexer Sätze von Zeichenanweisungen zu verbessern.
Ein Beispiel für die Verwendung von CompositingStrategy.Offscreen
ist BlendModes
. Angenommen, Sie möchten im folgenden Beispiel Teile einer Image
-Composable-Funktion entfernen, indem Sie einen Zeichenbefehl mit BlendMode.Clear
ausgeben. Wenn Sie compositingStrategy
nicht auf CompositingStrategy.Offscreen
festlegen, interagiert BlendMode
mit allen Inhalten darunter.
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 ) ) } } )
Wenn Sie CompositingStrategy
auf Offscreen
setzen, wird eine Offscreen-Textur erstellt, in der die Befehle ausgeführt werden (BlendMode
wird nur auf den Inhalt dieses Composables angewendet). Es wird dann über dem gerendert, was bereits auf dem Bildschirm gerendert wurde, ohne die bereits gezeichneten Inhalte zu beeinträchtigen.

Wenn Sie CompositingStrategy.Offscreen
nicht verwendet haben, werden durch die Anwendung von BlendMode.Clear
alle Pixel im Ziel gelöscht, unabhängig davon, was bereits festgelegt war. Der Rendering-Puffer des Fensters (schwarz) bleibt sichtbar. Viele BlendModes
, bei denen der Alphakanal verwendet wird, funktionieren ohne Offscreen-Puffer nicht wie erwartet. Achten Sie auf den schwarzen Ring um die rote Kreisanzeige:

Wenn die App einen durchscheinenden Fensterhintergrund hatte und Sie CompositingStrategy.Offscreen
nicht verwendet haben, interagiert BlendMode
mit der gesamten App. Dabei werden alle Pixel gelöscht, um die darunter liegende App oder das Hintergrundbild anzuzeigen, wie in diesem Beispiel:

Wenn Sie CompositingStrategy.Offscreen
verwenden, wird eine Offscreen-Textur in der Größe des Zeichenbereichs erstellt und auf dem Bildschirm gerendert. Alle Zeichenbefehle, die mit dieser Strategie ausgeführt werden, werden standardmäßig auf diese Region zugeschnitten. Das folgende Code-Snippet veranschaulicht die Unterschiede beim Umstellen auf die Verwendung von Offscreen-Texturen:
@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
Diese Kompositionsstrategie moduliert den Alphawert für jede der in graphicsLayer
aufgezeichneten Zeichenanweisungen. Es wird kein Offscreen-Puffer für Alpha unter 1,0 f erstellt, sofern kein RenderEffect
festgelegt ist.Das kann die Alpha-Wiedergabe effizienter machen. Bei sich überschneidenden Inhalten können jedoch unterschiedliche Ergebnisse erzielt werden. In Anwendungsfällen, in denen im Voraus bekannt ist, dass sich Inhalte nicht überschneiden, kann dies eine bessere Leistung als CompositingStrategy.Auto
mit Alphawerten unter 1 bieten.
Ein weiteres Beispiel für verschiedene Kompositionsstrategien sehen Sie unten. Hier werden unterschiedliche Alphas auf verschiedene Teile der Composables angewendet und eine Modulate
-Strategie verwendet:
@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)

Inhalt einer Composable-Funktion in eine Bitmap schreiben
Ein häufiger Anwendungsfall ist das Erstellen eines Bitmap
aus einer Composable-Funktion. Wenn Sie den Inhalt Ihres Composables in ein Bitmap
kopieren möchten, erstellen Sie mit rememberGraphicsLayer()
ein GraphicsLayer
.
Leiten Sie die Zeichenbefehle mit drawWithContent()
und graphicsLayer.record{}
an die neue Ebene weiter. Zeichnen Sie die Ebene dann mit drawLayer
auf dem sichtbaren Canvas:
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) }
Sie können die Bitmap auf der Festplatte speichern und freigeben. Weitere Informationen finden Sie im vollständigen Beispiel-Snippet. Prüfen Sie, ob Berechtigungen auf dem Gerät vorhanden sind, bevor Sie versuchen, auf der Festplatte zu speichern.
Modifikator für benutzerdefinierte Zeichnungen
Wenn Sie einen eigenen benutzerdefinierten Modifier erstellen möchten, implementieren Sie die DrawModifier
-Schnittstelle. Dadurch erhalten Sie Zugriff auf ein ContentDrawScope
, das dem entspricht, was bei Verwendung von Modifier.drawWithContent()
verfügbar ist. Sie können dann allgemeine Zeichenvorgänge in benutzerdefinierte Zeichenmodifikatoren extrahieren, um den Code zu bereinigen und praktische Wrapper bereitzustellen. Modifier.background()
ist beispielsweise ein praktischer DrawModifier
.
Wenn Sie beispielsweise einen Modifier
implementieren möchten, der Inhalte vertikal spiegelt, können Sie ihn so erstellen:
class FlippedModifier : DrawModifier { override fun ContentDrawScope.draw() { scale(1f, -1f) { this@draw.drawContent() } } } fun Modifier.flipped() = this.then(FlippedModifier())
Verwenden Sie dann diesen umgekehrten Modifikator für Text
:
Text( "Hello Compose!", modifier = Modifier .flipped() )

Zusätzliche Ressourcen
Weitere Beispiele für die Verwendung von graphicsLayer
und benutzerdefinierten Zeichnungen finden Sie in den folgenden Ressourcen:
Empfehlungen für dich
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Grafiken in Compose
- Bild anpassen {:#customize-image}
- Kotlin für Jetpack Compose