Grafikmodifikatoren

Zusätzlich zur komponierbaren Funktion Canvas bietet Compose mehrere nützliche Modifiers-Grafiken, die beim Zeichnen benutzerdefinierter Inhalte helfen. Diese Modifikatoren sind nützlich, weil sie auf jede beliebige zusammensetzbare Funktion angewendet werden können.

Zeichenmodifikatoren

Alle Zeichenbefehle werden in Compose mit einem Zeichenmodifikator ausgeführt. In Compose gibt es drei Hauptmodifikatoren für das Zeichnen:

Der Basismodifikator für das Zeichnen ist drawWithContent. Damit können Sie die Zeichenreihenfolge Ihrer zusammensetzbaren Funktion und die Zeichenbefehle im Modifikator festlegen. drawBehind ist ein praktischer Wrapper für drawWithContent, bei dem die Zeichenreihenfolge so festgelegt ist, dass der Inhalt hinter dem Inhalt der zusammensetzbaren Funktion gezeichnet wird. drawWithCache ruft entweder onDrawBehind oder onDrawWithContent auf und bietet einen Mechanismus zum Caching der darin erstellten Objekte.

Modifier.drawWithContent: Zeichenreihenfolge auswählen

Mit Modifier.drawWithContent können Sie DrawScope-Vorgänge vor oder nach dem Inhalt des Komponierbaren ausführen. Achten Sie darauf, drawContent aufzurufen, um den tatsächlichen Inhalt der kombinierbaren 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 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 Benutzeroberfläche im Taschenlampenstil zu erstellen.

Modifier.drawBehind: Hinter einer zusammensetzbaren Funktion zeichnen

Mit Modifier.drawBehind können Sie DrawScope-Vorgänge hinter dem auf dem Bildschirm angezeigten zusammensetzbaren Inhalt ausführen. Wenn Sie sich die Implementierung von Canvas ansehen, werden Sie feststellen, dass es sich nur um einen praktischen Wrapper für 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)
)

Dadurch wird das folgende Ergebnis erzeugt:

Text und Hintergrund, die mit Modifier.drawBehind gezeichnet wurden
Abbildung 2: Text und Hintergrund, die mit „Modifier.drawBehind“ gezeichnet wurden

Modifier.drawWithCache: Zeichnen und Caching von Zeichnungsobjekten

Modifier.drawWithCache speichert die darin erstellten Objekte im Cache. Die Objekte werden im Cache gespeichert, solange die Größe des Zeichenbereichs gleich bleibt oder sich die gelesenen Zustandsobjekte nicht geändert haben. Dieser Modifikator ist nützlich, um die Leistung von Zeichenbefehlen zu verbessern, da Objekte (z. B. Brush, Shader, Path), 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. Dies 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 ein Brush-Objekt erstellen, um einen Farbverlauf hinter einem Text-Objekt 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())
                )
            }
        }
)

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

Grafikmodifikatoren

Modifier.graphicsLayer: Transformationen auf Composables anwenden

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

  • Isolierung für die Zeichenanweisungen (ähnlich wie RenderNode). Zeichenanweisungen, die als Teil einer Ebene erfasst werden, können von der Rendering-Pipeline effizient neu ausgegeben werden, ohne dass der Anwendungscode noch einmal ausgeführt werden muss.
  • Transformationen, die für alle in einer Ebene enthaltenen Zeichnungsanweisungen gelten.
  • Rasterung für Kompositionsfunktionen. Wenn eine Ebene gerastert wird, werden ihre Zeichenanweisungen ausgeführt und die Ausgabe wird in einem Offscreen-Zwischenspeicher erfasst. Das Compositing eines solchen Puffers 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 Drehung angewendet werden.

Transformationen

Modifier.graphicsLayer bietet Isolation für die Zeichenanweisungen. Beispielsweise können verschiedene Transformationen mit Modifier.graphicsLayer angewendet werden. Diese können animiert oder geändert werden, ohne dass das Zeichen-Lambda noch einmal ausgeführt werden muss.

Modifier.graphicsLayer ändert nicht die gemessene Größe oder Platzierung Ihrer komponierbaren Funktion, da es 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. Der Wert 1.0f bedeutet, dass die Größe nicht geändert wird. Der Wert 0.5f bedeutet, dass die Größe halbiert wird.

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 ein Bild-Composable angewendet
Übersetzung

translationX und translationY können mit graphicsLayer geändert werden. translationX verschiebt die komponierbare Funktion nach links oder rechts. translationY verschiebt die komponierbare 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 auf Bild mit Modifier.graphicsLayer angewendet
Rotation

Legen Sie rotationX für die horizontale Drehung, rotationY für die vertikale Drehung und rotationZ für die Drehung um die Z-Achse (Standarddrehung) fest. 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
        }
)

Abbildung 6: rotationX, rotationY und rotationZ, die mit Modifier.graphicsLayer für ein Bild festgelegt wurden
Ursprung

Es kann ein transformOrigin angegeben werden. Er wird dann als Ausgangspunkt für Transformationen verwendet. In allen bisherigen Beispielen wurde TransformOrigin.Center verwendet, das sich unter (0.5f, 0.5f) befindet. Wenn Sie den Ursprung bei (0f, 0f) angeben, beginnen die Transformationen in der oberen linken Ecke des Composables.

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

Clip und Form

„Shape“ gibt den Umriss an, auf den der Inhalt bei clip = true zugeschnitten wird. In diesem Beispiel legen wir für zwei Boxen zwei verschiedene Clips fest – einen mit der Clip-Variablen graphicsLayer und den anderen 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:

Clip auf Box-Komposition angewendet
Abbildung 8: Auf Box-Composable angewendeter Clip

Wenn Sie dann einen translationY auf den oberen rosa Kreis anwenden, sehen Sie, dass die Grenzen der komponierbaren Funktion unverändert sind, der Kreis aber unter dem unteren Kreis (und außerhalb seiner Grenzen) gezeichnet wird.

Clip mit translationY und rotem Rahmen für die Umrandung angewendet
Abbildung 9: Clip mit translationY und rotem Rahmen für die Kontur

Wenn Sie die komponierbare Funktion auf den Bereich beschränken möchten, in dem sie gezeichnet wird, können Sie am Anfang der Modifikatorkette ein weiteres 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 zusätzlich zur graphicsLayer-Transformation angewendet wird
Abbildung 10: Clip, der über der graphicsLayer-Transformation angewendet wird

Alpha

Mit Modifier.graphicsLayer kann eine alpha (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 Alphawert

Compositing-Strategie

Die Arbeit mit Alpha und Transparenz ist nicht immer so einfach wie das Ändern eines einzelnen Alphawerts. Zusätzlich zum Ändern eines Alphawerts gibt es auch die Möglichkeit, einen CompositingStrategy für einen graphicsLayer festzulegen. Ein CompositingStrategy bestimmt, wie der Inhalt des Composables mit den anderen Inhalten, die bereits auf dem Bildschirm angezeigt werden, zusammengesetzt wird.

Die verschiedenen Strategien sind:

Automatisch (Standard)

Die Zusammensetzungsstrategie wird durch die restlichen graphicsLayer-Parameter bestimmt. Die Ebene wird in einem Offscreen-Zwischenspeicher gerendert, wenn der Alphawert kleiner als 1,0f ist oder ein RenderEffect festgelegt ist. 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 Alphawert am Ziel zu zeichnen. Wenn Sie RenderEffect oder Overscroll festlegen, werden Inhalte immer in einem Offscreen-Zwischenspeicher gerendert, unabhängig davon, ob CompositingStrategy festgelegt ist.

Nicht sichtbar

Der Inhalt der komponierbaren Funktion wird immer in eine Offscreen-Textur oder -Bitmap gerastert, bevor er auf dem Ziel gerendert wird. Das ist nützlich, um BlendMode-Vorgänge zum Maskieren von Inhalten anzuwenden und die Leistung beim Rendern komplexer Zeichnungsanweisungen zu verbessern.

Ein Beispiel für die Verwendung von CompositingStrategy.Offscreen ist BlendModes. Im folgenden Beispiel möchten Sie Teile eines Image-Kompositums entfernen, indem Sie einen Zeichenbefehl mit BlendMode.Clear ausgeben. Wenn Sie compositingStrategy nicht auf CompositingStrategy.Offscreen setzen, 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 setzen, wird eine Offscreen-Textur erstellt, auf der die Befehle ausgeführt werden. Dabei wird BlendMode nur auf den Inhalt dieser zusammensetzbaren Funktion angewendet. Sie wird dann über dem gerendert, was bereits auf dem Bildschirm gerendert wurde, ohne den bereits gezeichneten Inhalt zu beeinflussen.

Modifier.drawWithContent auf einem Bild, das eine kreisförmige Anzeige mit BlendMode.Clear in der App zeigt
Abbildung 12: Modifier.drawWithContent auf einem Bild, das eine Kreisanzeige mit BlendMode.Clear und CompositingStrategy.Offscreen in der App zeigt

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 der BlendModes, die Alpha beinhalten, funktionieren ohne Offscreen-Zwischenspeicher nicht wie erwartet. Beachte den schwarzen Ring um die rote Kreisanzeige:

Modifier.drawWithContent auf einem Bild, das eine Kreisanzeige mit BlendMode.Clear und ohne festgelegte CompositingStrategy zeigt
Abbildung 13: Modifier.drawWithContent auf einem Bild mit einer kreisförmigen Anzeige, mit BlendMode.Clear und ohne CompositingStrategy

Zur Verdeutlichung: 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 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. Der rosafarbene Hintergrund ist im Bereich um den roten Statuskreis zu sehen.
Abbildung 14: Keine CompositingStrategy festgelegt und Verwendung von BlendMode.Clear mit einer App, die einen durchscheinenden Fensterhintergrund hat. Beachte, dass der pinkfarbene Hintergrund im Bereich um den roten Statuskreis zu sehen ist.

Wenn Sie CompositingStrategy.Offscreen verwenden, wird eine Offscreen-Textur in der Größe des Zeichenbereichs erstellt und auf dem Bildschirm gerendert. 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 versus CompositingStrategy.Offscreen – Offscreen schneidet den Bereich ab, Auto nicht
Abbildung 15: CompositingStrategy.Auto im Vergleich zu CompositingStrategy.Offscreen – Offscreen schneidet den Bereich ab, Auto nicht
ModulateAlpha

Diese Kompositionsstrategie moduliert den Alphawert für jede der im graphicsLayer aufgezeichneten Zeichenanweisungen. Es wird kein Offscreen-Zwischenspeicher für Alpha-Werte unter 1,0f erstellt, es sei denn, es ist ein RenderEffect festgelegt.Das kann für das Alpha-Rendering effizienter sein. Es kann jedoch unterschiedliche Ergebnisse für sich überschneidende Inhalte liefern. Bei Anwendungsfällen, in denen im Voraus bekannt ist, dass sich Inhalte nicht überschneiden, kann dies eine bessere Leistung als CompositingStrategy.Auto mit Alphawerten unter 1 bieten.

Im folgenden Beispiel werden verschiedene Kompositionsstrategien demonstriert: Es werden verschiedene Alphas auf verschiedene Teile der Composables angewendet und eine Modulate-Strategie wird 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)
    )
}

@Preview
@Composable
fun ModifierGraphicsLayerOutsets() {
    Column(
        modifier = Modifier
            .background(Color(0xFF0A0E14))
            .padding(48.dp),
        verticalArrangement = Arrangement.spacedBy(48.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        val pillShape = RoundedCornerShape(percent = 50)

        // Without outsets: alpha = 0.5f creates an offscreen buffer that clips the 24.dp glow
        Box(
            modifier = Modifier
                .size(
                    width = 140.dp,
                    height = 56.dp
                )
                .graphicsLayer(
                    alpha = 0.5f,
                    clip = false
                )
                .dropShadow(
                    shape = pillShape,
                    shadow = Shadow(radius = 24.dp, color = Color.Cyan)
                )
                .background(color = Color(0xFF141824), shape = pillShape),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "✨ Glow",
                color = Color.White,
                fontSize = 18.sp
            )
        }

        Box(
            modifier = Modifier
                .size(
                    width = 140.dp,
                    height = 56.dp
                )
                .graphicsLayer(
                    alpha = 0.5f,
                    outsets = LayerOutsets(24.dp),
                    clip = false
                )
                .dropShadow(
                    shape = pillShape,
                    shadow = Shadow(radius = 24.dp, color = Color.Cyan)
                )
                .background(color = Color(0xFF141824), shape = pillShape),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "✨ Glow",
                color = Color.White,
                fontSize = 18.sp
            )
        }
    }
}


val Purple = Color(0xFF7E57C2)
val Yellow = Color(0xFFFFCA28)
val Red = Color(0xFFEF5350)

„ModulateAlpha“ wendet den Alphawert auf jeden einzelnen Zeichenbefehl an
Abbildung 16: „ModulateAlpha“ wendet den Alphawert auf jeden einzelnen Zeichenbefehl an

Ebenen-Outsets

Wenn eine zusammensetzbare Funktion in einen Offscreen-Zwischenspeicher gezeichnet wird (z. B. wenn alpha kleiner als 1.0f ist, ein RenderEffect festgelegt ist oder CompositingStrategy.Offscreen verwendet wird), wird die Offscreen-Textur standardmäßig auf die gemessenen Layoutgrenzen der zusammensetzbaren Funktion festgelegt. Alle Zeichnungen oder Schatten innerhalb der Ebene, die über die Layoutgrenzen des Composables hinausgehen (z. B. ein mit Modifier.dropShadow gezeichnetes äußeres Leuchten), werden implizit an der Grenze des Offscreen-Puffers abgeschnitten.

Anstatt Modifier.padding() hinzuzufügen (wodurch die Layoutmessung benachbarter UI-Elemente verschoben wird), übergeben Sie LayerOutsets an den Parameter outsets von Modifier.graphicsLayer. Dadurch werden die visuellen Grenzen des Offscreen-Puffers auf jeder Seite erweitert, ohne dass sich die gemessene Layoutgröße des Composeables ändert. Beachten Sie, dass LayerOutsets die Clipform oder die Schattenbegrenzungen der Ebene nicht erweitert. Diese basieren weiterhin auf der ursprünglich gemessenen Größe des Composeables. Behalten Sie daher clip = false für graphicsLayer bei, um ein Abschneiden an den ursprünglichen Begrenzungen zu vermeiden:

Box(
    modifier = Modifier
        .size(
            width = 140.dp,
            height = 56.dp
        )
        .graphicsLayer(
            alpha = 0.5f,
            outsets = LayerOutsets(24.dp),
            clip = false
        )
        .dropShadow(
            shape = pillShape,
            shadow = Shadow(radius = 24.dp, color = Color.Cyan)
        )
        .background(color = Color(0xFF141824), shape = pillShape),
    contentAlignment = Alignment.Center
) {
    Text(
        text = "✨ Glow",
        color = Color.White,
        fontSize = 18.sp
    )
}

Vergleich eines äußeren Leuchtens einer Offscreen-Ebene ohne LayerOutsets (oben, auf die rechteckigen Layoutgrenzen zugeschnitten) und mit LayerOutsets(24.dp) (unten, nicht zugeschnitten)
Abbildung 17: Äußerer Schein einer Offscreen-Ebene ohne LayerOutsets (oben, an den rechteckigen Layoutgrenzen des Composables abgeschnitten) im Vergleich zu mit LayerOutsets(24.dp) (unten, nicht abgeschnitten)

Inhalt einer zusammensetzbaren Funktion in eine Bitmap schreiben

Ein häufiger Anwendungsfall ist das Erstellen eines Bitmap aus einer zusammensetzbaren Funktion. 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 freigeben. Weitere Informationen finden Sie im vollständigen Beispiel-Snippet. Prüfen Sie die Geräteberechtigungen, bevor Sie versuchen, auf dem Laufwerk zu speichern.

Benutzerdefinierter Zeichnungsmodifikator

Wenn Sie einen eigenen benutzerdefinierten Modifikator erstellen möchten, implementieren Sie die Schnittstelle DrawModifier. Dadurch erhalten Sie Zugriff auf ein ContentDrawScope, das mit dem bei Verwendung von Modifier.drawWithContent() bereitgestellten Objekt identisch ist. 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 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())

Verwenden Sie dann diesen umgekehrten Modifikator, der auf Text angewendet wird:

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

Benutzerdefinierter umgekehrter Modifikator für Text
Abbildung 17: Benutzerdefinierter umgekehrter Modifikator für Text

Zusätzliche Ressourcen

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