Zusätzlich zu den Canvas
-Kompositionen bietet Compose mehrere nützliche GrafikenModifiers
, mit denen sich benutzerdefinierte Inhalte erstellen lassen. Diese Modifikatoren sind nützlich, da sie auf jedes Kompositionenelement angewendet werden können.
Zeichenmodifikatoren
Alle Zeichenbefehle werden in Compose mit einem Zeichenmodifikator ausgeführt. In Compose gibt es drei Hauptmodifikatoren für das Zeichnen:
Der Basis-Modifikator für das Zeichnen ist drawWithContent
. Hier können Sie die Zeichnungsreihenfolge Ihres Composables und die Zeichnungsbefehle festlegen, die im Modifikator ausgegeben werden. drawBehind
ist ein praktischer Wrapper um drawWithContent
, wobei die Zeichenreihenfolge hinter dem Inhalt der zusammensetzbaren Funktion festgelegt ist. drawWithCache
ruft entweder onDrawBehind
oder onDrawWithContent
auf und bietet einen Mechanismus zum Caching der darin erstellten Objekte.
Modifier.drawWithContent
: Zeichnungsreihenfolge auswählen
Mit Modifier.drawWithContent
können Sie DrawScope
-Vorgänge vor oder nach dem Inhalt des Composeables ausführen. Rufen Sie drawContent
auf, um den Inhalt des Composeables zu rendern. Mit diesem Modifikator können Sie die Reihenfolge der Vorgänge festlegen, wenn Ihr Inhalt vor oder nach den benutzerdefinierten Zeichenvorgängen gezeichnet werden soll.
Wenn Sie beispielsweise einen radialen Farbverlauf über Ihren Inhalt rendern möchten, um einen Taschenlampen-Schlüssellocheffekt auf der Benutzeroberfläche zu erzeugen, gehen Sie so vor:
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
: Hinter einem Composeable zeichnen
Mit Modifier.drawBehind
können Sie DrawScope
-Vorgänge hinter den zusammensetzbaren Inhalten ausführen, die auf dem Bildschirm angezeigt werden. Wenn Sie sich die Implementierung von Canvas
ansehen, stellen Sie möglicherweise fest, dass es sich dabei nur um eine praktische Ummantelung von Modifier.drawBehind
handelt.
So zeichnen Sie hinter Text
ein abgerundetes Rechteck:
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
: Zeichnen und Caching von Zeichnungsobjekten
Modifier.drawWithCache
speichert die darin erstellten Objekte im Cache. Die Objekte werden im Cache gespeichert, solange die Größe des Zeichenbereichs gleich bleibt oder sich die gelesenen Statusobjekte nicht geändert haben. Dieser Modifikator ist nützlich, um die Leistung von Zeichenaufrufen zu verbessern, da Objekte, die bei „draw“ erstellt werden (z. B. Brush, Shader, Path
), nicht neu zugewiesen werden müssen.
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
im Cache gespeichert, 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 Elemente anwenden
Modifier.graphicsLayer
ist ein Modifikator, mit dem der Inhalt des Composeables in eine Zeichenebene gezeichnet wird. Eine Schicht bietet verschiedene Funktionen, z. B.:
- Isolation der Zeichenanweisungen (ähnlich
RenderNode
). Zeichenanweisungen, die als Teil einer Ebene erfasst wurden, können von der Renderingpipeline effizient neu ausgegeben werden, ohne Anwendungscode noch einmal ausführen zu müssen. - Transformationen, die für alle Zeichenanweisungen in einer Ebene gelten.
- Rasterisierung für Kompositionsmöglichkeiten Wenn eine Ebene gerastert wird, werden ihre Zeichenanweisungen ausgeführt und die Ausgabe wird in einem Zwischenspeicher außerhalb des Bildschirms erfasst. Das Zusammenstellen eines solchen Zwischenspeichers für nachfolgende Frames ist schneller als das Ausführen der einzelnen Anweisungen. Er verhält sich jedoch wie eine Bitmap, wenn Transformationen wie Skalierung oder Rotation angewendet werden.
Transformation
Modifier.graphicsLayer
bietet eine Isolation für die Zeichenanweisungen. So können beispielsweise verschiedene Transformationen mit Modifier.graphicsLayer
angewendet werden.
Diese können animiert oder geändert werden, ohne dass die Lambda-Zeichnung noch einmal ausgeführt werden muss.
Modifier.graphicsLayer
ändert weder die Größe noch die Platzierung des Composeables, da es sich nur auf die Zeichenphase auswirkt. Das bedeutet, dass sich Ihr Composeable über andere Elemente legen kann, wenn es außerhalb seiner Layoutgrenzen gezeichnet wird.
Mit diesem Modifikator können die folgenden Transformationen angewendet werden:
Skalierung – Größe erhöhen
Mit scaleX
und scaleY
können Sie Inhalte horizontal oder vertikal vergrößern oder verkleinern. Ein Wert von 1.0f
bedeutet keine Änderung der Skalierung, ein Wert von 0.5f
bedeutet 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 die zusammensetzbare Funktion nach links oder rechts. Mit translationY
kannst du das Composeable nach oben oder unten verschieben.
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, rotationY
für die vertikale und rotationZ
für die Drehung um die 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. Sie wird dann als Punkt verwendet, von dem aus Transformationen stattfinden. 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 oberen linken Ecke der zusammensetzbaren Funktion.
Wenn Sie den Ursprung mit einer rotationZ
-Transformation ändern, sehen Sie, dass sich das Element oben links im Composeable dreht:
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 } )
Zuschneiden und Formen
Die Form gibt den Umriss an, an dem der Inhalt abgeschnitten wird, wenn clip = true
. In diesem Beispiel haben wir zwei Boxen mit zwei verschiedenen Clips eingerichtet – eine mit der Clipvariablen graphicsLayer
und die andere 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 „Hallo Compose“) wird auf die Kreisform zugeschnitten:
Wenn Sie dann ein translationY
auf den oberen rosa Kreis anwenden, sehen Sie, dass die Grenzen des zusammensetzbaren Elements immer noch gleich sind, aber der Kreis unter dem unteren Kreis (und außerhalb seiner Grenzen) gezeichnet wird.
Wenn Sie das Composeable auf den Bereich zuschneiden möchten, in dem es gezeichnet wird, können Sie am Anfang der Modifikatorkette eine weitere Modifier.clip(RectangleShape)
hinzufügen. Der Inhalt bleibt 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
Die Arbeit mit Alpha und Transparenz ist möglicherweise nicht so einfach wie das Ändern eines einzelnen Alphawerts. Neben der Änderung einer Alphaversion gibt es auch die Möglichkeit, ein CompositingStrategy
für eine graphicsLayer
festzulegen. Mit einer CompositingStrategy
wird festgelegt, wie die Inhalte des Composeables mit den anderen Inhalten kombiniert werden, die bereits auf dem Bildschirm dargestellt werden.
Die verschiedenen Strategien sind:
Automatisch (Standard)
Die Kompositionsstrategie wird durch die übrigen graphicsLayer
-Parameter bestimmt. Die Ebene wird in einen Offscreen-Puffer gerendert, wenn der Alphawert kleiner als 1,0 f ist oder RenderEffect
festgelegt ist. Wenn der Alphawert unter 1f liegt, wird automatisch eine Kompositionierungsebene erstellt, um den Inhalt zu rendern und dann diesen Offscreen-Puffer mit dem entsprechenden Alphawert an das Ziel zu zeichnen. Wenn du RenderEffect
oder Overscroll festlegst, werden Inhalte unabhängig von der festgelegten CompositingStrategy
immer in einem Offscreen-Puffer gerendert.
Nicht sichtbar
Der Inhalt des Composeables wird immer in eine Offscreen-Textur oder -Bitmap gerastert, bevor er an das Ziel gerendert wird. Das ist nützlich, um BlendMode
-Vorgänge zum Maskieren von Inhalten anzuwenden und die Leistung beim Rendern komplexer Zeichenanweisungen zu verbessern.
Ein Beispiel für die Verwendung von CompositingStrategy.Offscreen
ist BlendModes
. Nehmen wir an, Sie möchten Teile einer zusammensetzbaren Image
-Funktion entfernen, indem Sie einen Zeichenbefehl ausführen, der BlendMode.Clear
verwendet. Wenn Sie compositingStrategy
nicht auf CompositingStrategy.Offscreen
setzen, interagiert BlendMode
mit allen darunter liegenden Inhalten.
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 du CompositingStrategy
auf Offscreen
setzt, wird eine Offscreen-Textur erstellt, auf die die Befehle ausgeführt werden. BlendMode
wird dabei nur auf den Inhalt dieses Composeables angewendet. Dieser wird dann über dem bereits gerenderten Inhalt auf dem Bildschirm gerendert, ohne dass sich dies auf den bereits gezeichneten Inhalt auswirkt.
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 wurde. Der Rendering-Puffer des Fensters (schwarz) bleibt sichtbar. Viele der BlendModes
-Funktionen, die Alpha beinhalten, funktionieren ohne Offscreen-Puffer nicht wie erwartet. Beachten Sie den schwarzen Ring um den roten Kreis:
Zum besseren Verständnis: Wenn die App einen durchscheinenden Fensterhintergrund hätte und du CompositingStrategy.Offscreen
nicht verwendet hast, interagiert BlendMode
mit der gesamten App. Es werden alle Pixel gelöscht, um die App oder den Hintergrund darunter anzuzeigen, wie in diesem Beispiel:
Hinweis: Wenn Sie CompositingStrategy.Offscreen
verwenden, wird eine Offscreen-Textur erstellt, die der Größe des Zeichenbereichs entspricht, und wieder auf dem Bildschirm gerendert. Alle Zeichnungsbefehle, die mit dieser Strategie ausgeführt werden, werden standardmäßig auf diese Region zugeschnitten. Das folgende Code-Snippet veranschaulicht die Unterschiede beim Wechsel zu 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
Bei dieser Kompositionsstrategie wird der Alphawert für jede der im graphicsLayer
aufgezeichneten Zeichenanweisungen moduliert. Es wird kein Offscreen-Zwischenspeicher für Alphaversionen unter 1,0f erstellt, es sei denn, RenderEffect
ist festgelegt, sodass er beim Alpha-Rendering effizienter sein kann. Bei sich überschneidenden Inhalten kann es jedoch zu unterschiedlichen Ergebnissen kommen. Bei Anwendungsfällen, bei denen im Voraus bekannt ist, dass sich Inhalte nicht überschneiden, kann dies eine bessere Leistung als CompositingStrategy.Auto
mit Alphawerten unter 1 bieten.
Unten sehen Sie ein weiteres Beispiel für verschiedene Kompositionsstrategien: Unterschiedliche Alphas werden auf verschiedene Teile der Composeables angewendet und eine Modulate
-Strategie wird 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 eines Composeables in eine Bitmap schreiben
Ein häufiger Anwendungsfall ist das Erstellen einer Bitmap
aus einem Composeable. Wenn Sie den Inhalt der zusammensetzbaren Funktion in ein Bitmap
kopieren möchten, erstellen Sie mit rememberGraphicsLayer()
eine GraphicsLayer
.
Leiten Sie die Zeichenbefehle mithilfe von drawWithContent()
und graphicsLayer.record{}
an die neue Ebene weiter. Zeichnen Sie dann die Ebene 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) }
Du kannst die Bitmap auf der Festplatte speichern und sie teilen. Weitere Informationen finden Sie im vollständigen Beispiel-Snippet. Prüfen Sie, ob Sie die erforderlichen Berechtigungen auf dem Gerät haben, bevor Sie versuchen, auf dem Laufwerk zu speichern.
Modifikator für benutzerdefinierte Zeichnungen
Wenn Sie einen eigenen benutzerdefinierten Modifikator erstellen möchten, implementieren Sie die DrawModifier
-Schnittstelle. So erhalten Sie Zugriff auf einen ContentDrawScope
, der mit dem identisch ist, der bei Verwendung von Modifier.drawWithContent()
freigegeben wird. Sie können dann häufig verwendete Zeichenvorgänge in benutzerdefinierte Zeichenmodifikatoren extrahieren, um den Code zu bereinigen und praktische Wrapper bereitzustellen. Modifier.background()
ist beispielsweise eine praktische DrawModifier
.
Wenn Sie beispielsweise eine Modifier
implementieren möchten, die Inhalte vertikal dreht, können Sie sie 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 auf Text
:
Text( "Hello Compose!", modifier = Modifier .flipped() )
Weitere Informationen
Weitere Beispiele für die Verwendung von graphicsLayer
und benutzerdefinierten Zeichnungen finden Sie in den folgenden Ressourcen:
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Grafiken in Compose
- Bild anpassen {:#customize-image}
- Kotlin für Jetpack Compose