Neben der zusammensetzbaren Funktion Canvas
gibt es in der Funktion „Compose“ mehrere 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 der Funktion „Compose“ mit einem Zeichenmodifikator ausgeführt. In der Funktion „Compose“ gibt es drei Hauptmodifikatoren zum Zeichnen:
Der Basismodifikator für das Zeichnen ist drawWithContent
. Damit können Sie die Zeichenreihenfolge der zusammensetzbaren Funktion und die Zeichenbefehle festlegen, die im Modifizierer ausgegeben werden. drawBehind
ist ein praktischer Wrapper um drawWithContent
, bei dem die Zeichenreihenfolge hinter dem Inhalt der zusammensetzbaren Funktion festgelegt ist. drawWithCache
ruft entweder onDrawBehind
oder onDrawWithContent
darin auf und bietet einen Mechanismus zum Speichern der darin erstellten Objekte im Cache.
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 drawContent
auf, um den tatsächlichen Inhalt der zusammensetzbaren Funktion zu rendern. Mit diesem Modifikator können Sie die Reihenfolge der Vorgänge festlegen, wenn Ihr Inhalt vor oder nach Ihren benutzerdefinierten Zeichenvorgängen dargestellt werden soll.
Wenn Sie beispielsweise einen radialen Farbverlauf über Ihren Inhalten rendern möchten, um auf der Benutzeroberfläche einen Taschenlampen-Schlüssellocheffekt 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 einer zusammensetzbaren Funktion
Mit Modifier.drawBehind
können Sie DrawScope
-Vorgänge hinter den zusammensetzbaren Inhalten ausführen, die auf dem Bildschirm dargestellt werden. Wenn Sie sich die Implementierung von Canvas
ansehen, werden Sie möglicherweise feststellen, dass es nur ein praktischer Wrapper um Modifier.drawBehind
ist.
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) )
Dies führt zu folgendem Ergebnis:
Modifier.drawWithCache
: Zeichenobjekte zeichnen und im Cache speichern
Modifier.drawWithCache
speichert die darin erstellten Objekte im Cache. 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, da keine Objekte (z. B. Brush, Shader, Path
) neu zugewiesen werden müssen, die beim Zeichnen erstellt werden.
Alternativ können Sie Objekte auch mit remember
außerhalb des Modifizierers im Cache speichern. Das ist jedoch nicht immer möglich, da Sie nicht immer Zugriff auf die Komposition haben. Die Verwendung von drawWithCache
ist möglicherweise leistungsfähiger, 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 mithilfe von drawWithCache
im Cache gespeichert, bis sich die Größe des Zeichnungsbereichs ä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 Funktion in einer Zeichenebene dargestellt wird. Eine Ebene bietet verschiedene Funktionen, z. B.:
- Isolation der Zeichenanweisungen (ähnlich wie bei
RenderNode
). Zeichenanweisungen, die als Teil einer Ebene erfasst wurden, können von der Rendering-Pipeline effizient neu ausgegeben werden, ohne Anwendungscode neu ausführen zu müssen. - Transformationen, die für alle Zeichenanweisungen in einer Ebene gelten.
- Rasterung für Kompositionsfunktionen. Bei der Rasterung einer Ebene werden ihre Zeichenanweisungen ausgeführt und die Ausgabe wird in einem nicht sichtbaren Zwischenspeicher erfasst. Das Zusammensetzen 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
isoliert die Zeichenanweisungen. Mit Modifier.graphicsLayer
können beispielsweise verschiedene Transformationen 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 Platzierung der zusammensetzbaren Funktion, sondern wirkt sich nur auf die Zeichenphase aus. Das bedeutet, dass Ihre zusammensetzbare Funktion andere überlappen kann, wenn sie am Ende außerhalb der Layoutgrenzen dargestellt wird.
Die folgenden Transformationen können mit diesem Modifikator angewendet werden:
Skalierung – Größe erhöhen
Mit scaleX
und scaleY
wird der Inhalt in horizontale bzw. vertikale Richtung vergrößert bzw. verkleinert. Ein Wert von 1.0f
gibt an, dass sich der Maßstab nicht ändert, ein Wert von 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 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
Legen Sie rotationX
für eine horizontale Drehung, rotationY
für eine vertikale Anzeigenrotation und rotationZ
für eine Rotation auf der Z-Achse fest (Standarddrehung). 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. Es wird dann als der Punkt verwendet, von dem aus Transformationen stattfinden. Bisher wurde in allen Beispielen TransformOrigin.Center
verwendet, der 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 das Element oben links in der zusammensetzbaren Funktion rotiert:
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, auf den der Inhalt zugeschnitten wird, wenn clip = true
. In diesem Beispiel legen wir zwei Felder so fest, dass sie zwei verschiedene Clips haben: einen mit der Clip-Variable graphicsLayer
und der 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 an die Kreisform gekürzt:
Wenn Sie dann ein translationY
auf den oberen rosa Kreis anwenden, sehen Sie, dass die Grenzen der zusammensetzbaren Funktion immer noch gleich sind, aber der Kreis wird unter dem unteren Kreis (und außerhalb seiner Grenzen) gezeichnet.
Wenn Sie die zusammensetzbare Funktion auf den Bereich zuschneiden möchten, in dem sie gezeichnet ist, 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 die Deckkraft (alpha
) für die gesamte Ebene festgelegt werden. 1.0f
ist vollständig deckend und 0.0f
ist 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 dem Ändern einer Alphaversion können Sie auch einen CompositingStrategy
für eine graphicsLayer
festlegen. Ein CompositingStrategy
bestimmt, wie der Inhalt der zusammensetzbaren Funktion mit den anderen Inhalten, die bereits auf dem Bildschirm dargestellt sind, zusammengesetzt wird.
Folgende Strategien stehen zur Verfügung:
Automatisch (Standard)
Die Zusammensetzungsstrategie wird durch die restlichen graphicsLayer
-Parameter bestimmt. Die Ebene wird in einen nicht sichtbaren Zwischenspeicher gerendert, wenn der Alphawert kleiner als 1,0f ist oder ein RenderEffect
festgelegt ist. Immer wenn der Alphawert kleiner als 1f ist, wird automatisch eine Erstellungsebene erstellt, um die Inhalte zu rendern und dann diesen nicht sichtbaren Zwischenspeicher mit dem entsprechenden Alphatest an das Ziel zu übertragen. Wenn Sie RenderEffect
oder Overscroll festlegen, werden Inhalte unabhängig vom festgelegten CompositingStrategy
immer in einem nicht sichtbaren Zwischenspeicher gerendert.
Nicht sichtbar
Der Inhalt der zusammensetzbaren Funktion wird vor dem Rendern für das Ziel immer auf eine nicht sichtbare Textur oder Bitmap gerastert. Dies ist nützlich, um BlendMode
-Vorgänge zum Maskieren von Inhalten anzuwenden und um die Leistung beim Rendern komplexer Sätze von Zeichenanweisungen zu verbessern.
Ein Beispiel für die Verwendung von CompositingStrategy.Offscreen
ist BlendModes
. Im folgenden Beispiel möchten Sie Teile einer zusammensetzbaren Funktion Image
entfernen, indem Sie einen Zeichenbefehl ausführen, der BlendMode.Clear
verwendet. Wenn Sie compositingStrategy
nicht auf CompositingStrategy.Offscreen
setzen, interagiert BlendMode
mit seinem gesamten Inhalt.
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 können. Dabei wird BlendMode
nur auf den Inhalt dieser zusammensetzbaren Funktion angewendet. Diese wird dann über dem bereits auf dem Bildschirm gerenderten Inhalt gerendert, ohne dass sich dies auf die bereits gezeichneten Inhalte 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 Renderingzwischenspeicher des Fensters (Schwarz) bleibt sichtbar. Viele der BlendModes
mit Alpha funktionieren ohne einen Offscreen-Zwischenspeicher nicht wie erwartet. Beachten Sie den schwarzen Ring um den roten Kreisindikator:
Zum besseren Verständnis: Wenn die App einen durchscheinenden Fensterhintergrund hätte und Sie CompositingStrategy.Offscreen
nicht verwenden würden, würde BlendMode
mit der gesamten App interagieren. Alle Pixel würden alle Pixel löschen, um die App oder den Hintergrund darunter anzuzeigen, wie in diesem Beispiel:
Wenn Sie CompositingStrategy.Offscreen
verwenden, wird eine nicht sichtbare Textur in der Größe des Zeichenbereichs erstellt und wieder auf dem Bildschirm gerendert. Alle Zeichenbefehle, die mit dieser Strategie ausgeführt werden, werden standardmäßig auf diese Region beschränkt. Das folgende Code-Snippet zeigt die Unterschiede beim Wechsel zu 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 Zusammensetzungsstrategie moduliert den Alphawert für jede der Zeichenanweisungen, die in graphicsLayer
aufgezeichnet wurden. Für Alpha-Werte unter 1,0f wird kein Offline-Zwischenspeicher erstellt, es sei denn, RenderEffect
ist festgelegt, sodass das Alpha-Rendering effizienter sein kann. Sie können jedoch zu unterschiedlichen Ergebnissen bei sich überschneidenden Inhalten führen. Für Anwendungsfälle, in denen im Voraus bekannt ist, dass sich die Inhalte nicht überschneiden, kann dies eine bessere Leistung als CompositingStrategy.Auto
mit Alphawerten unter 1 bieten.
Weiter unten finden Sie ein weiteres Beispiel für verschiedene Zusammensetzungsstrategien. Hier werden verschiedene Alphas auf verschiedene Teile der zusammensetzbaren Funktionen angewendet und es wird eine Modulate
-Strategie angewendet:
@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)
Inhalt einer zusammensetzbaren Funktion in eine Bitmap schreiben
Ein häufiger Anwendungsfall besteht darin, eine Bitmap
aus einer zusammensetzbaren Funktion zu erstellen. Wenn Sie den Inhalt Ihrer zusammensetzbaren Funktion 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 dann die Ebene mit drawLayer
im 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 dem Laufwerk speichern und teilen. Weitere Informationen finden Sie im vollständigen Beispiel-Snippet. Überprüfen Sie die Geräteberechtigungen, bevor Sie Dateien speichern.
Benutzerdefinierter Modifikator für Zeichnung
Implementieren Sie die DrawModifier
-Schnittstelle, um einen eigenen benutzerdefinierten Modifikator zu erstellen. Dadurch erhalten Sie Zugriff auf ein ContentDrawScope
-Objekt, das mit dem Wert übereinstimmt, der bei Verwendung von Modifier.drawWithContent()
bereitgestellt wird. Anschließend können Sie gängige 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 ein Modifier
implementieren möchten, das Inhalte vertikal dreht, können Sie Folgendes erstellen:
class FlippedModifier : DrawModifier { override fun ContentDrawScope.draw() { scale(1f, -1f) { this@draw.drawContent() } } } fun Modifier.flipped() = this.then(FlippedModifier())
Verwende dann diesen umgedrehten Modifikator, der auf Text
angewendet wird:
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