Zusätzlich zu den zusammensetzbaren Canvas
-Funktionen gibt es in Compose mehrere nützliche Grafiken Modifiers
, die Sie beim Zeichnen benutzerdefinierter Inhalte unterstützen. Diese Modifikatoren sind nützlich, da sie auf jede zusammensetzbare Funktion angewendet werden können.
Zeichenmodifikatoren
Alle Zeichenbefehle werden mit einem Zeichenmodifikator in Compose ausgeführt. In Compose gibt es drei Hauptmodifikatoren für Zeichnungen:
Der Basismodifikator für das Zeichnen ist drawWithContent
. Damit können Sie die Zeichenreihenfolge des kompilierbaren Elements und die Zeichenbefehle 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
darin 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 der zusammensetzbaren Funktion ausführen. Rufen Sie unbedingt drawContent
auf, um den eigentlichen Inhalt der zusammensetzbaren Funktion 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 Inhalten rendern möchten, um auf der Benutzeroberfläche einen Taschenlampen-Keyhole-Effekt zu erzeugen, 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
: Zeichnung hinter einer zusammensetzbaren Funktion
Mit Modifier.drawBehind
können Sie DrawScope
-Vorgänge für zusammensetzbare Inhalte ausführen, die auf dem Bildschirm gezeichnet werden. Wenn Sie sich die Implementierung von Canvas
ansehen, werden Sie feststellen, dass es sich nur um einen praktischen Wrapper um 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
: Zeichenobjekte zeichnen und im Cache speichern
In Modifier.drawWithCache
werden die darin erstellten Objekte im Cache gespeichert. Die Objekte werden im Cache gespeichert, solange die Größe des Zeichenbereichs gleich ist oder sich gelesene Statusobjekte nicht geändert haben. Dieser Modifikator ist nützlich, um die Leistung von Zeichenaufrufen zu verbessern. Objekte, die beim Zeichnen erstellt werden, müssen nicht neu zugewiesen werden (z. B. Brush, Shader, Path
).
Alternativ können Sie Objekte auch mit remember
außerhalb des Modifikators im Cache speichern. Dies ist jedoch nicht immer möglich, da Sie nicht immer Zugriff auf die Komposition haben. drawWithCache
ist möglicherweise leistungsstärker, 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 zusammensetzbare Funktionen anwenden
Modifier.graphicsLayer
ist ein Modifikator, mit dem der Inhalt der zusammensetzbaren Zeichnung zu einer Zeichenebene wird. Eine Ebene 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.
- Rasterung für Kompositionsfunktionen. 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 Isolierung der Zeichenanweisungen. Beispielsweise können 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 nicht die gemessene Größe oder Position der zusammensetzbaren Funktion, da sie sich nur auf die Zeichenphase auswirkt. Dies bedeutet, dass Ihre zusammensetzbare Funktion andere überlappen kann, wenn sie außerhalb ihrer 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 in horizontaler bzw. vertikaler Richtung vergrößert oder verkleinert. Der Wert 1.0f
bedeutet, dass die Skalierung nicht geändert wurde, 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. translationY
verschiebt die zusammensetzbare Funktion nach oben oder unten.
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.translationX = 100.dp.toPx() this.translationY = 10.dp.toPx() } )
Drehung
Stellen Sie rotationX
für eine horizontale Drehung, rotationY
für eine vertikale Drehung und rotationZ
für eine Drehung um die Z-Achse (Standarddrehung) ein. 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. Bei allen bisherigen Beispielen wurde TransformOrigin.Center
verwendet, der sich bei (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 um die obere linke Ecke der zusammensetzbaren Funktion 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 } )
Clip und Form
Die Form gibt den Umriss an, an dem der Inhalt abgeschnitten wird, wenn clip = true
. In diesem Beispiel legen wir zwei Kästchen mit zwei verschiedenen Clips fest – eines mit der Clip-Variable graphicsLayer
und das 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 „Hello Compose“) wird auf die Kreisform zugeschnitten:
Wenn Sie dann ein translationY
auf den oberen rosa Kreis anwenden, sehen Sie, dass die Begrenzungen des zusammensetzbaren Elements immer noch gleich sind, aber der Kreis unterhalb des unteren Kreises (und außerhalb seiner Begrenzungen) gezeichnet wird.
Um die zusammensetzbare Funktion auf den Bereich zu beschränken, in dem sie gezeichnet wurde, können Sie am Anfang der Modifikatorkette ein weiteres Modifier.clip(RectangleShape)
-Element 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 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 einzelnen Alphawerts. Neben der Änderung einer Alphaversion gibt es auch die Möglichkeit, ein CompositingStrategy
für eine graphicsLayer
festzulegen. Ein CompositingStrategy
bestimmt, wie der Inhalt der zusammensetzbaren Funktion mit den anderen bereits auf dem Bildschirm gezeichneten Inhalten zusammengesetzt wird.
Die verschiedenen Strategien sind:
Automatisch (Standard)
Die Zusammensetzungsstrategie wird durch den Rest der graphicsLayer
-Parameter bestimmt. Die Ebene wird in einen Zwischenspeicher außerhalb des Bildschirms gerendert, wenn der Alphawert unter 1.0f liegt oder ein RenderEffect
festgelegt ist. Immer wenn der Alphawert kleiner als 1f ist, wird automatisch eine Compositing-Ebene erstellt, um den Inhalt zu rendern und dann diesen Offscreen-Zwischenspeicher mit dem entsprechenden Alphakanal in das Ziel zu ziehen. Wenn Sie RenderEffect
oder Overscroll festlegen, werden Inhalte unabhängig vom festgelegten CompositingStrategy
immer in einem Zwischenspeicher außerhalb des Bildschirms gerendert.
Nicht sichtbar
Der Inhalt der zusammensetzbaren Funktion wird immer auf einer nicht sichtbaren Textur oder Bitmap gerastert, bevor sie im Ziel gerendert werden. Dies ist nützlich, um BlendMode
-Vorgänge zum Maskieren von Inhalten anzuwenden und beim Rendern komplexer Gruppen von Zeichenanweisungen die Leistung 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 Sie CompositingStrategy
auf Offscreen
setzen, wird eine nicht sichtbare Textur erstellt, auf die die Befehle ausgeführt werden. Dabei wird BlendMode
nur auf den Inhalt dieser zusammensetzbaren Funktion angewendet. Das Bild wird über dem bereits auf dem Bildschirm gerenderten Inhalt gerendert, ohne dass dies Auswirkungen auf die bereits gezeichneten Inhalte hat.
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-Zwischenspeicher des Fensters (schwarz) bleibt sichtbar. Viele BlendModes
mit Alpha funktionieren ohne einen Offscreen-Zwischenspeicher nicht wie erwartet. Achten Sie auf 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:
Bei Verwendung von CompositingStrategy.Offscreen
wird eine nicht sichtbare Textur erstellt, die der Größe des Zeichenbereichs entspricht, und wieder auf dem Bildschirm gerendert. Alle Zeichenbefehle, die mit dieser Strategie ausgeführt werden, werden standardmäßig auf diesen Bereich begrenzt. Das folgende Code-Snippet veranschaulicht die Unterschiede bei der Verwendung von nicht sichtbaren 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-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 unterschiedliche Ergebnisse liefern. 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.
Ein weiteres Beispiel für verschiedene Kompositionsstrategien ist das Anwenden verschiedener Alphas auf verschiedene Teile der zusammensetzbaren Funktionen und das Anwenden einer 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. Wenn Sie den Inhalt der zusammensetzbaren Funktion in eine 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
in den 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 teilen. Weitere Informationen findest du im vollständigen Beispiel-Snippet. Vor dem Speichern auf der Festplatte sollten Sie die Geräteberechtigungen prüfen.
Benutzerdefinierter Zeichnungsmodifikator
Implementieren Sie die DrawModifier
-Schnittstelle, um einen eigenen benutzerdefinierten Modifizierer zu erstellen. Dadurch erhalten Sie Zugriff auf ein ContentDrawScope
-Objekt, das auch bei Verwendung von Modifier.drawWithContent()
verfügbar ist. Anschließend können Sie gängige Zeichenvorgänge in benutzerdefinierte Zeichenmodifikatoren extrahieren, um den Code zu bereinigen und praktische Wrapper bereitzustellen. Beispielsweise ist Modifier.background()
eine praktische DrawModifier
.
Wenn Sie beispielsweise ein Modifier
implementieren möchten, das den Inhalt vertikal umdreht, können Sie es 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 umgedrehten Modifikator auf Text
:
Text( "Hello Compose!", modifier = Modifier .flipped() )
Zusätzliche Ressourcen
Weitere Beispiele zur 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