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 der Inhalt vor oder nach den benutzerdefinierten Zeichenvorgängen gezeichnet werden soll.

Wenn Sie beispielsweise einen radialen Farbverlauf über dem Inhalt rendern möchten, um einen Taschenlampen-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 Taschenlampen-Benutzeroberfläche 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 Objekte (z. B. Brush, Shader, Path usw.), die beim Zeichnen erstellt werden, 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. Es kann leistungsfähiger sein, drawWithCache zu verwenden, wenn die Objekte nur zum Zeichnen verwendet werden.

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

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 das Zeichen-Lambda neu ausgeführt werden muss.

Modifier.graphicsLayer ändert nicht die gemessene Größe oder Platzierung der zusammensetzbaren Funktion, da er sich nur auf die Zeichenphase auswirkt. Das bedeutet, dass sich die 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

Mit scaleX und scaleY wird der Inhalt in horizontaler bzw. vertikaler Richtung vergrößert oder verkleinert. 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, angewendet auf eine zusammensetzbare Funktion vom Typ „Image“
Übersetzung

translationX und translationY können mit graphicsLayer geändert werden. Mit translationX wird die zusammensetzbare Funktion nach links oder rechts verschoben. Mit translationY wird die zusammensetzbare Funktion nach oben oder unten verschoben.

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, angewendet auf ein Bild mit Modifier.graphicsLayer
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, festgelegt für ein Bild mit Modifier.graphicsLayer
Ursprung

Ein transformOrigin kann angegeben werden. Er 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, angewendet mit TransformOrigin, festgelegt auf 0f, 0f

Clip und Form

„Shape“ gibt die Kontur an, an die der Inhalt angeclipst 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 an die Kreisform angeclipst:

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

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, angewendet mit translationY, und roter Rahmen für die Kontur

Wenn Sie die zusammensetzbare Funktion auf den Bereich beschränken 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, angewendet über der graphicsLayer-Transformation

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

CompositingStrategy

Die Arbeit mit Alpha und Transparenz ist möglicherweise nicht 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 dem anderen Inhalt zusammengesetzt wird, der bereits auf dem Bildschirm gezeichnet wurde.

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 unter 1.0f liegt oder ein RenderEffect festgelegt ist. Wenn der Alpha-Wert unter 1f liegt, wird automatisch eine Compositing-Ebene erstellt, um den Inhalt zu rendern und diesen Offscreen-Puffer dann mit dem entsprechenden Alpha-Wert in das Ziel zu zeichnen. Wenn Sie einen RenderEffect oder Overscroll festlegen, wird der Inhalt immer in einen Offscreen Puffer gerendert, unabhängig von der CompositingStrategy festgelegt.

Nicht sichtbar

Der Inhalt der zusammensetzbaren Funktion wird immer in eine Offscreen-Textur oder -Bitmap rasterisiert, bevor er im Ziel gerendert wird. Dies 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 sind BlendModes. Nehmen wir an, 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 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 Sie CompositingStrategy auf Offscreen festlegen, wird eine Offscreen-Textur erstellt, um die Befehle auszuführen (wobei BlendMode nur auf den Inhalt 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 mit „BlendMode.Clear“ gezeichnet wird 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 App oder das Hintergrundbild darunter 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 beschränkt. 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 funktioniert
Abbildung 15: CompositingStrategy.Auto im Vergleich zu CompositingStrategy.Offscreen – Offscreen wird auf den Bereich beschränkt, 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 unterschiedlichen Ergebnissen führen. In Anwendungsfällen, in denen im Voraus bekannt ist, dass sich Inhalte nicht überlappen, kann diese Strategie eine bessere Leistung als CompositingStrategy.Auto mit Alpha-Werten unter 1 bieten.

Ein weiteres Beispiel für verschiedene Compositing-Strategien: Hier werden unterschiedliche Alpha-Werte auf verschiedene Teile der zusammensetzbaren Funktionen angewendet und eine Modulate-Strategie 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)

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

Inhalt 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 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 den Inhalt 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 Modifier für gespiegelten 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: