Zusätzlich zu den zusammensetzbaren Funktionen für Canvas
bietet „Compose“ einige nützliche Grafiken
Modifiers
, die beim Zeichnen benutzerdefinierter Inhalte helfen. Diese Modifikatoren sind nützlich,
da sie auf jede zusammensetzbare Funktion angewendet werden können.
Zeichenmodifikatoren
Alle Zeichenbefehle werden in Compose mit einem Zeichenmodifikator ausgeführt. Es gibt drei Hauptmodifikatoren für Zeichnungen in Compose:
Der Basismodifikator für das Zeichnen ist drawWithContent
. Hier können Sie festlegen,
der zusammensetzbaren Zeichen
und der Zeichenbefehle, die im
Modifikator. drawBehind
ist ein praktischer Wrapper um drawWithContent
,
die Zeichenreihenfolge hinter dem Inhalt der zusammensetzbaren Funktion. drawWithCache
ruft entweder onDrawBehind
oder onDrawWithContent
darin auf und stellt eine
zum Zwischenspeichern der darin erstellten Objekte.
Modifier.drawWithContent
: Zeichnungsreihenfolge auswählen
Mit Modifier.drawWithContent
können Sie
DrawScope
-Vorgänge vor oder nach dem Inhalt der
zusammensetzbar. Rufen Sie unbedingt drawContent
auf, damit der eigentliche Inhalt
der zusammensetzbaren Funktion. 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 einen Keyhole-Effekt für die Taschenlampe in der Benutzeroberfläche erzeugen möchten, können Sie so vorgehen:
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, die auf dem Bildschirm gezeichnet werden. Wenn
Wenn Sie sich die Implementierung von Canvas
ansehen, stellen Sie möglicherweise fest,
ist nur ein praktischer Wrapper um Modifier.drawBehind
.
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
: Zeichenobjekte zeichnen und im Cache speichern
Modifier.drawWithCache
behält die Objekte bei
die im Cache erstellt wurden. Die Objekte werden im Cache gespeichert,
oder Statusobjekte, die gelesen werden,
geändert. Dieser Modifikator ist nützlich, um die Leistung von Zeichenaufrufen zu verbessern,
Dadurch müssen Objekte nicht neu zugewiesen werden (z. B. Brush, Shader, Path
).
die durch Zeichnen erstellt wurden.
Alternativ können Sie Objekte auch mithilfe von remember
außerhalb des
Modifikator. Dies ist jedoch nicht immer möglich, da Sie nicht immer Zugriff haben
auf die Komposition. drawWithCache
kann leistungsfähiger sein, wenn die
-Objekte werden nur zum Zeichnen verwendet.
Wenn Sie beispielsweise ein Brush
erstellen, um einen Farbverlauf hinter einem Text
zu zeichnen, verwenden Sie
drawWithCache
speichert das Brush
-Objekt im Cache, bis die Größe des Zeichenbereichs erreicht ist
Änderungen:
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 zusammensetzbare Funktionen 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 für die zugehörigen Zeichenanweisungen (ähnlich
RenderNode
). Zeichnen als Teil einer Ebene erfasste Anweisungen können effizient vom Rendering-Pipeline aus, ohne den Anwendungscode noch einmal auszuführen. - Transformationen, die für alle Zeichenanweisungen in einem Ebene.
- Rasterung für Kompositionsfunktionen. Wenn eine Ebene gerastert wird, Die Zeichenanweisungen werden ausgeführt und die Ausgabe wird außerhalb des Bildschirms festgehalten. Puffer. Das Compositing eines solchen Buffers für nachfolgende Frames ist schneller als die Ausführung der einzelnen Anweisungen. Er verhält sich jedoch wie eine Bitmap, wenn Transformationen wie Skalierung oder Drehung angewendet werden.
Transformation
Modifier.graphicsLayer
isoliert die Zeichenanweisungen. für
können verschiedene Transformationen mit Modifier.graphicsLayer
angewendet werden.
Diese können animiert oder geändert werden, ohne dass die Zeichnung erneut ausgeführt werden muss.
Lambda.
Modifier.graphicsLayer
hat keinen Einfluss auf die gemessene Größe oder Position des Geräts
zusammensetzbar, da es sich nur auf die Zeichenphase auswirkt. Das bedeutet, dass Ihre zusammensetzbare Funktion
können andere überlappen, wenn sie außerhalb der Layoutgrenzen gezeichnet wird.
Die folgenden Transformationen können mit diesem Modifikator angewendet werden:
Skalieren – Größe erhöhen
Mit scaleX
und scaleY
wird der Inhalt horizontal oder vertikal vergrößert oder verkleinert.
Richtung. Der Wert 1.0f
gibt an, dass die Skalierung nicht geändert wurde,
0.5f
steht für 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. translationY
verschiebt den
die zusammensetzbar sind.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.translationX = 100.dp.toPx() this.translationY = 10.dp.toPx() } )
Ausrichtung
Stellen Sie rotationX
für eine horizontale Drehung, rotationY
für eine vertikale Drehung und
Mit rotationZ
können Sie sie um die Z-Achse drehen (Standardrotation). Dieser Wert ist angegeben
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 der Punkt verwendet, von dem aus
finden Transformationen statt. Bei allen bisherigen Beispielen wurde
TransformOrigin.Center
, Ort: (0.5f, 0.5f)
. Wenn Sie den Ursprung in
(0f, 0f)
beginnen, beginnen die Transformationen dann links oben im
zusammensetzbar.
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
In diesem Beispiel haben wir zwei Felder mit zwei verschiedenen Clips eingerichtet:
graphicsLayer
-Variable des Typs „Clip“ 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 Feldes (der Text „Hallo Nachricht“) wird auf die Kreisform:
Wenn Sie dann ein translationY
auf den oberen rosa Kreis anwenden, sehen Sie, dass die Grenzen
der zusammensetzbaren Elemente sind immer noch dieselben, aber der Kreis wird unterhalb der Unterseite angezeigt.
Kreis (und außerhalb der Grenzen) liegt.
Um die zusammensetzbare Funktion auf den dargestellten Bereich zu beschneiden, können Sie eine weitere
Modifier.clip(RectangleShape)
am Anfang der Modifikatorkette. Inhalt
bleibt 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
können Sie eine alpha
(Deckkraft) für die gesamte Ebene festlegen. 1.0f
ist vollständig deckend und 0.0f
nicht sichtbar.
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
Alphawert. Neben dem Ändern eines Alpha-Werts gibt es auch die Option, eine
CompositingStrategy
auf graphicsLayer
. Ein CompositingStrategy
bestimmt, wie die
Inhalt der zusammensetzbaren Funktion mit den anderen
die bereits auf dem Bildschirm zu sehen sind.
Die verschiedenen Strategien sind:
Automatisch (Standard)
Die Compositing-Strategie wird durch den Rest der graphicsLayer
bestimmt.
Parameter. Er rendert die Ebene in einen Offscreen-Zwischenspeicher, wenn Alpha kleiner als ist.
1.0f oder ein RenderEffect
ist festgelegt. Immer wenn der Alphawert kleiner als 1f ist, wird ein
Compositing-Ebene wird automatisch erstellt, um den Inhalt zu rendern, und
mit dem entsprechenden Alpha-Track
an das Ziel übergeben. Festlegen eines
Durch RenderEffect
oder Overscroll werden Inhalte immer außerhalb des Bildschirms gerendert
Puffer unabhängig vom festgelegten CompositingStrategy
fest.
Nicht im Bild
Der Inhalt der zusammensetzbaren Funktion wird immer außerhalb des Bildschirms gerastert.
Textur oder Bitmap vor dem Rendern im Ziel. Dies ist nützlich für
Anwenden von BlendMode
-Vorgängen zum Maskieren von Inhalten und für die Leistung, wenn
komplexe Reihen von Zeichenanweisungen gerendert.
Ein Beispiel für die Verwendung von CompositingStrategy.Offscreen
ist BlendModes
. Sehen wir uns das Beispiel unten an.
Sie möchten Teile einer zusammensetzbaren Image
-Funktion entfernen, indem Sie einen Draw-Befehl ausführen,
verwendet BlendMode.Clear
. Wenn Sie compositingStrategy
nicht auf
CompositingStrategy.Offscreen
, interagiert der 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 du „CompositingStrategy
“ auf Offscreen
setzt, wird ein Out-of-Screen erstellt,
Textur, auf die die Befehle ausgeführt werden sollen. Dabei wird BlendMode
nur auf die
Inhalt dieser zusammensetzbaren Funktion). Dann wird sie auf Basis des bereits vorhandenen
ohne Auswirkungen auf den bereits gezeichneten Inhalt zu haben.
Wenn Sie CompositingStrategy.Offscreen
nicht verwendet haben,
BlendMode.Clear
löscht alle Pixel im Ziel, unabhängig davon,
wurde bereits festgelegt, sodass der Rendering-Zwischenspeicher (schwarz) des Fensters sichtbar blieb. Viele von
BlendModes
, die Alpha beinhalten, funktionieren ohne eine
aus. Beachten Sie den schwarzen Ring um den roten Kreis:
Zum besseren Verständnis: Hat die App ein durchscheinendes Fenster?
Hintergrund und Sie haben CompositingStrategy.Offscreen
nicht verwendet, die
BlendMode
würde mit der gesamten App interagieren. Es werden alle Pixel gelöscht,
darunter die App oder den Hintergrund, wie in diesem Beispiel:
Beachten Sie, dass bei der Verwendung von CompositingStrategy.Offscreen
ein nicht auf dem Bildschirm
Textur, die die Größe des Zeichenbereichs hat, wird erstellt und auf dem
Bildschirm. Alle Zeichenbefehle, die mit dieser Strategie ausgeführt werden, sind standardmäßig
auf diesen Bereich zugeschnitten. Das folgende Code-Snippet veranschaulicht die Unterschiede beim
zur Verwendung von Texturen außerhalb des sichtbaren Bereichs:
@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 Alpha-Track für jede der
Anweisungen, die in graphicsLayer
aufgezeichnet wurden. Es wird kein Offscreen-Puffer für Alphawerte unter 1.0f erstellt, es sei denn, RenderEffect
ist festgelegt. Das kann für das Alpha-Rendering effizienter sein. Sie kann jedoch zu anderen Ergebnissen führen,
für sich überschneidende Inhalte. Für Anwendungsfälle, bei denen im Voraus bekannt ist,
nicht überlappen, kann dies eine bessere Leistung erzielen als
CompositingStrategy.Auto
mit Alphawerten unter 1.
Ein weiteres Beispiel für verschiedene
Kompositionsstrategien finden Sie unten.
Alphas auf verschiedene Teile der zusammensetzbaren Funktionen anwenden und eine Modulate
Strategie:
@Preview @Composable fun CompositingStratgey_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)
Inhalte einer zusammensetzbaren Funktion in eine Bitmap schreiben
Ein häufiger Anwendungsfall ist das Erstellen einer Bitmap
aus einer zusammensetzbaren Funktion. Zum Kopieren der
zu Bitmap
erstellen Sie ein GraphicsLayer
mit
rememberGraphicsLayer()
Leiten Sie die Zeichenbefehle mit drawWithContent()
an die neue Ebene um und
graphicsLayer.record{}
. Zeichnen Sie dann die Ebene mit
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) }
Du kannst die Bitmap auf der Festplatte speichern und teilen. Weitere Informationen finden Sie in der vollständigen Beispiel-Snippet. Prüfe die Geräteberechtigungen, bevor du es versuchst zum Speichern auf der Festplatte.
Modifikator für benutzerdefinierte Zeichnungen
Implementieren Sie die DrawModifier
-Schnittstelle, um einen eigenen benutzerdefinierten Modifizierer zu erstellen. 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 z. B. eine Modifier
implementieren möchten, die den
können Sie wie folgt einen erstellen:
class FlippedModifier : DrawModifier { override fun ContentDrawScope.draw() { scale(1f, -1f) { this@draw.drawContent() } } } fun Modifier.flipped() = this.then(FlippedModifier())
Verwenden Sie dann diesen umgedrehten 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 {:#customize-image}
- Kotlin für Jetpack Compose