Grafikmodifikatoren

Neben der zusammensetzbaren Funktion Canvas bietet Compose mehrere nützliche Grafik Modifiers, die beim Zeichnen benutzerdefinierter Inhalte helfen. Diese Modifikatoren sind nützlich, da sie auf jede zusammensetzbare Funktion angewendet werden können.

Zeichnungsmodifikatoren

Alle Zeichenbefehle werden in Compose mit einem Zeichnungsmodifikator ausgeführt. Es gibt drei Hauptzeichnungsmodifikatoren in Compose:

Der Basismodifikator für das Zeichnen ist drawWithContent. Hier können Sie die Zeichenreihenfolge Ihrer zusammensetzbaren Funktion und die Zeichenbefehle festlegen, die innerhalb des Modifikators ausgegeben werden. drawBehind ist ein praktischer Wrapper um drawWithContent, bei dem die Zeichenreihenfolge auf „Hinter dem Inhalt der zusammensetzbaren Funktion“ festgelegt ist. drawWithCache ruft entweder onDrawBehind oder onDrawWithContent auf und bietet einen Mechanismus zum Speichern der darin erstellten Objekte im Cache.

Modifier.drawWithContent: Zeichenreihenfolge auswählen

Modifier.drawWithContent ermöglicht es Ihnen, DrawScope-Vorgänge vor oder nach dem Inhalt der zusammensetzbaren Funktion auszufü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 Sie möchten, dass Ihre Inhalte vor oder nach Ihren benutzerdefinierten Zeichenvorgängen gezeichnet werden.

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 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
}

Abbildung 1: Modifier.drawWithContent wird über einer zusammensetzbaren Funktion verwendet, um eine Benutzeroberfläche mit Taschenlampeneffekt zu erstellen.

Modifier.drawBehind: Hinter einer zusammensetzbaren Funktion zeichnen

Modifier.drawBehind ermöglicht Ihnen die Ausführung von DrawScope-Vorgängen hinter dem Inhalt der zusammensetzbaren Funktion, der auf dem Bildschirm gezeichnet wird. 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 Ergebnis sieht so aus:

Text und ein mit Modifier.drawBehind gezeichneter Hintergrund
Abbildung 2: Text und Hintergrund, die mit Modifier.drawBehind gezeichnet wurden

Modifier.drawWithCache: Zeichenobjekte zeichnen und im Cache speichern

Modifier.drawWithCache speichert die darin erstellten Objekte im Cache. 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 leistungsstärker sein, wenn die Objekte nur zum Zeichnen verwendet werden.

Wenn Sie beispielsweise einen Brush erstellen, um einen Farbverlauf hinter einem Text zu zeichnen, speichert drawWithCache das Brush-Objekt im Cache, 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())
                )
            }
        }
)

Brush-Objekt mit drawWithCache im Cache speichern
Abbildung 3: Das Brush-Objekt mit drawWithCache im Cache speichern

Grafikmodifikatoren

Modifier.graphicsLayer: Transformationen auf zusammensetzbare Funktionen anwenden

Modifier.graphicsLayer ist ein Modifikator, mit dem der Inhalt der zusammensetzbaren Funktion in eine Zeichenebene gezeichnet wird. Eine Ebene bietet einige verschiedene Funktionen, z. B.:

  • Isolation für die Zeichenanweisungen (ähnlich wie RenderNode). Zeichen anweisungen, 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 angewendet werden, die in einer Ebene enthalten sind.
  • Rasterisierung für Kompositionsfunktionen. Wenn eine Ebene rasterisiert wird, werden die 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. Er verhält sich jedoch wie eine Bitmap, wenn Transformationen wie Skalierung oder Rotation angewendet werden.

Transformationen

Modifier.graphicsLayer bietet 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 Zeichen-Lambda-Funktion neu ausgeführt werden muss.

Modifier.graphicsLayer ändert nicht die gemessene Größe oder Platzierung Ihrer zusammensetzbaren Funktion, da er sich nur auf die Zeichenphase auswirkt. Das bedeutet, dass sich Ihre zusammensetzbare Funktion mit anderen überschneiden kann, wenn sie außerhalb ihrer Layoutgrenzen gezeichnet wird.

Die folgenden Transformationen können mit diesem Modifikator angewendet werden:

Skalieren – Größe erhöhen

scaleX und scaleY vergrößern oder verkleinern Inhalte in horizontaler bzw. vertikaler Richtung. 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
        }
)

Abbildung 4: scaleX und scaleY auf eine zusammensetzbare Funktion vom Typ „Image“ angewendet
Ü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()
        }
)

Abbildung 5: translationX und translationY mit Modifier.graphicsLayer auf ein Bild angewendet
Rotation

Legen Sie rotationX für die horizontale Rotation, rotationY für die vertikale Rotation und rotationZ für die Rotation auf der Z-Achse (Standardrotation) fest. Dieser Wert wird in Grad angegeben (0–360).

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

Abbildung 6: rotationX, rotationY und rotationZ mit Modifier.graphicsLayer auf ein Bild angewendet
Ursprung

Es kann ein transformOrigin angegeben werden. Dieser wird dann als Punkt verwendet, von dem aus Transformationen stattfinden. In allen bisherigen Beispielen wurde TransformOrigin.Center verwendet, das sich bei (0.5f, 0.5f) befindet. Wenn Sie den Ursprung bei (0f, 0f) angeben, beginnen die Transformationen in der linken oberen Ecke der zusammensetzbaren Funktion.

Wenn Sie den Ursprung mit einer rotationZ-Transformation ändern, sehen Sie, dass sich das Element um die linke obere 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
        }
)

Abbildung 7: Rotation mit TransformOrigin auf 0f, 0f festgelegt

Clip und Form

„Shape“ gibt die Kontur an, auf die der Inhalt zugeschnitten wird, wenn clip = true. In diesem Beispiel legen wir für zwei Felder zwei verschiedene Clips fest. Einer verwendet die graphicsLayer-Clipvariable und der andere den 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:

Clip auf Box-Composable angewendet
Abbildung 8: Clip auf die zusammensetzbare Funktion vom Typ „Box“ angewendet

Wenn Sie dann eine translationY auf den oberen rosa Kreis anwenden, sehen Sie, dass die Grenzen der zusammensetzbaren Funktion gleich bleiben, der Kreis aber unter dem unteren Kreis (und außerhalb seiner Grenzen) gezeichnet wird.

Clip mit „translationY“ und rotem Rahmen für die Kontur
Abbildung 9: Clip mit translationY angewendet und roter Rahmen für die Kontur

Wenn Sie die zusammensetzbare Funktion auf den Bereich zuschneiden möchten, in dem sie gezeichnet wird, können Sie am Anfang der Modifikatorkette einen weiteren 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))
    )
}

Clip, der auf die Transformation von „graphicsLayer“ angewendet wird
Abbildung 10: Clip über der graphicsLayer-Transformation angewendet

Alpha

Mit Modifier.graphicsLayer kann ein alpha-Wert (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
        }
)

Bild mit angewendetem Alphakanal
Abbildung 11: Bild mit angewendetem Alpha-Wert

CompositingStrategy

Das Arbeiten mit Alpha und Transparenz ist nicht immer so einfach wie das Ändern eines einzelnen Alpha-Werts. Neben dem Ändern eines Alpha-Werts besteht auch die Möglichkeit, eine CompositingStrategy für eine graphicsLayer festzulegen. Eine CompositingStrategy bestimmt, wie der Inhalt der zusammensetzbaren Funktion mit den anderen Inhalten zusammengesetzt wird, die bereits auf dem Bildschirm gezeichnet wurden.

Die verschiedenen Strategien sind:

Automatisch (Standard)

Die Compositing-Strategie wird durch die übrigen graphicsLayer Parameter bestimmt. Die Ebene wird in einen Offscreen-Puffer gerendert, wenn der Alpha-Wert kleiner als 1.0f ist oder ein RenderEffect festgelegt ist. Wenn der Alpha-Wert kleiner als 1f ist, wird automatisch eine Compositing-Ebene erstellt, um die Inhalte zu rendern und diesen Offscreen-Puffer dann mit dem entsprechenden Alpha-Wert in das Ziel zu zeichnen. Wenn Sie einen RenderEffect oder Overscroll festlegen, werden Inhalte immer in einen Offscreen Puffer gerendert, unabhängig von der CompositingStrategy festgelegten.

Offscreen

Die Inhalte der zusammensetzbaren Funktion werden immer in eine Offscreen-Textur oder -Bitmap rasterisiert, bevor sie in das Ziel gerendert werden. Dies ist nützlich, um BlendMode-Vorgänge zum Maskieren von Inhalten anzuwenden und um die Leistung beim Rendern komplexer Zeichenanweisungen zu verbessern.

Ein Beispiel für die Verwendung von CompositingStrategy.Offscreen sind BlendModes. Angenommen, Sie möchten Teile einer zusammensetzbaren Funktion vom Typ Image entfernen, indem Sie einen Zeichenbefehl ausgeben, der BlendMode.Clear verwendet. Wenn Sie compositingStrategy nicht auf CompositingStrategy.Offscreen festlegen, interagiert BlendMode mit allen Hintergrundinhalten.

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 festlegen, wird eine Offscreen-Textur erstellt, um die Befehle auszuführen (wobei BlendMode nur auf die Inhalte dieser zusammensetzbaren Funktion angewendet wird). Anschließend wird sie über dem bereits auf dem Bildschirm gerenderten Inhalt gerendert, ohne den bereits gezeichneten Inhalt zu beeinflussen.

Modifier.drawWithContent für ein Bild mit einem Kreis, der BlendMode.Clear in der App zeigt
Abbildung 12: Modifier.drawWithContent auf einem Bild mit einer Kreisanzeige, mit BlendMode.Clear und CompositingStrategy.Offscreen in der App

Wenn Sie CompositingStrategy.Offscreen nicht verwenden, 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 der BlendModes, die Alpha verwenden, funktionieren ohne Offscreen-Puffer nicht wie erwartet. Beachten Sie den schwarzen Ring um die rote Kreisanzeige:

„Modifier.drawWithContent“ für ein Bild mit einem Kreis, der „BlendMode.Clear“ zeigt und für den keine „CompositingStrategy“ festgelegt ist
Abbildung 13: Modifier.drawWithContent auf einem Bild mit einer Kreisanzeige, mit BlendMode.Clear und ohne festgelegte CompositingStrategy

Ein weiteres Beispiel: Wenn die App einen durchscheinenden Fensterhintergrund hat und Sie CompositingStrategy.Offscreen nicht verwenden, interagiert BlendMode mit der gesamten App. Alle Pixel werden gelöscht, um die darunter liegende App oder das Hintergrundbild anzuzeigen, wie in diesem Beispiel:

Es wurde keine CompositingStrategy festgelegt und BlendMode.Clear wird mit einer App verwendet, die einen durchscheinenden Fensterhintergrund hat. Die rosa Tapete ist im Bereich um den roten Statuskreis zu sehen.
Abbildung 14: Keine CompositingStrategy festgelegt und BlendMode.Clear mit einer App mit durchscheinendem Fensterhintergrund verwendet. Beachten Sie, wie das rosa Hintergrundbild durch den Bereich um den roten Statuskreis zu sehen ist.

Wenn Sie CompositingStrategy.Offscreen verwenden, wird eine Offscreen-Textur erstellt, die die Größe des Zeichenbereichs hat und wieder auf dem Bildschirm gerendert wird. Alle Zeichenbefehle, die mit dieser Strategie ausgeführt werden, werden standardmäßig auf diesen Bereich zugeschnitten. Das folgende Code-Snippet veranschaulicht die Unterschiede beim Wechsel zur 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()))
        }
    }
}

CompositingStrategy.Auto im Vergleich zu CompositingStrategy.Offscreen: Offscreen-Clips in der Region, in der „Auto“ nicht
Abbildung 15: CompositingStrategy.Auto im Vergleich zu CompositingStrategy.Offscreen – Offscreen wird auf den Bereich zugeschnitten, Auto nicht
ModulateAlpha

Diese Compositing-Strategie moduliert den Alpha-Wert für jede der Zeichen anweisungen, die in der graphicsLayer aufgezeichnet wurden. Für Alpha-Werte unter 1.0f wird kein Offscreen-Puffer erstellt, es sei denn, ein RenderEffect ist festgelegt. Daher kann diese Strategie für das Alpha-Rendering effizienter sein. Bei überlappenden Inhalten kann sie jedoch zu anderen Ergebnissen führen. In Anwendungsfällen, in denen im Voraus bekannt ist, dass sich Inhalte nicht überlappen, kann diese Strategie bei Alpha-Werten unter 1 eine bessere Leistung als CompositingStrategy.Auto bieten.

Im folgenden Beispiel werden verschiedene Compositing-Strategien veranschaulicht. Dabei werden verschiedene Alpha-Werte auf verschiedene Teile der zusammensetzbaren Funktionen angewendet und eine Modulate-Strategie angewendet:

@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)

„ModulateAlpha“ wendet den für jeden einzelnen Zeichenbefehl festgelegten Alphawert an.
Abbildung 16: ModulateAlpha wendet den für jeden einzelnen Zeichenbefehl festgelegten Alpha-Wert an

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 Ihrer zusammensetzbaren Funktion in eine Bitmap kopieren möchten, erstellen Sie mit rememberGraphicsLayer() eine GraphicsLayer.

Leiten Sie die Zeichenbefehle mit drawWithContent() und graphicsLayer.record{} an die neue Ebene weiter. Zeichnen Sie dann die Ebene mit drawLayer auf die sichtbare 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 On-Device-Berechtigungen vorhanden sind, bevor Sie versuchen, die Bitmap auf dem Laufwerk zu speichern.

Benutzerdefinierter Zeichnungsmodifikator

Wenn Sie einen eigenen benutzerdefinierten Modifikator erstellen möchten, implementieren Sie die DrawModifier-Schnittstelle. Dadurch erhalten Sie Zugriff auf einen ContentDrawScope, der mit dem identisch ist, der bei Verwendung von Modifier.drawWithContent() verfügbar gemacht wird. Sie können dann allgemeine Zeichenvorgänge in benutzerdefinierte Zeichnungsmodifikatoren 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())

Wenden Sie diesen gespiegelten Modifikator dann auf Text an:

Text(
    "Hello Compose!",
    modifier = Modifier
        .flipped()
)

Benutzerdefinierter gespiegelter Modifikator für Text
Abbildung 17: Benutzerdefinierter gespiegelter Modifikator auf Text

Zusätzliche Ressourcen

Weitere Beispiele für die Verwendung von graphicsLayer und benutzerdefinierten Zeichnungen finden Sie in den folgenden Ressourcen: