Schatten in Compose hinzufügen

Schatten heben die Benutzeroberfläche visuell hervor, weisen Nutzer auf Interaktivität hin und geben sofortiges Feedback zu Nutzeraktionen. Compose bietet mehrere Möglichkeiten, Schatten in Ihre App einzubinden:

  • Modifier.shadow(): Erstellt einen höhenbasierten Schatten hinter einem Compose-Element, der den Material Design-Richtlinien entspricht.
  • Modifier.dropShadow(): Erstellt einen anpassbaren Schatten, der hinter einer komponierbaren Funktion angezeigt wird, sodass sie erhöht erscheint.
  • Modifier.innerShadow(): Erstellt einen Schatten innerhalb der Grenzen einer komponierbaren Funktion, sodass sie so aussieht, als wäre sie in die Oberfläche dahinter gedrückt.

Modifier.shadow() eignet sich für einfache Schatten, während die Modifikatoren dropShadow() und innerShadow() eine genauere Steuerung und Präzision beim Rendern von Schatten bieten.

Auf dieser Seite wird beschrieben, wie Sie die einzelnen Modifikatoren implementieren. Dazu gehört auch, wie Sie Schatten bei Nutzerinteraktionen animieren und wie Sie die Modifikatoren innerShadow() und dropShadow() verketten, um Schatten mit Farbverlauf, neumorphe Schatten und mehr zu erstellen.

Einfache Schatten erstellen

Modifier.shadow() erstellt einen einfachen Schatten gemäß den Material Design-Richtlinien, der eine Lichtquelle von oben simuliert. Die Schattentiefe basiert auf einem elevation-Wert und der Schattenwurf wird auf die Form des Composeables zugeschnitten.

@Composable
fun ElevationBasedShadow() {
    Box(
        modifier = Modifier.aspectRatio(1f).fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Box(
            Modifier
                .size(100.dp, 100.dp)
                .shadow(10.dp, RectangleShape)
                .background(Color.White)
        )
    }
}

Ein grauer Schatten um eine weiße rechteckige Form.
Abbildung 1. Ein höhenbasierter Schatten, der mit Modifier.shadow() erstellt wurde.

Schlagschatten implementieren

Verwenden Sie den Modifikator dropShadow(), um einen genauen Schatten hinter Ihren Inhalten zu zeichnen, damit das Element erhöht erscheint.

Über den Parameter Shadow können Sie die folgenden wichtigen Aspekte steuern:

  • radius: Definiert die Weichheit und Diffusion der Unschärfe.
  • color: Definiert die Farbe der Tönung.
  • offset: Positioniert die Geometrie des Schattens entlang der x- und y-Achse.
  • spread: Steuert die Ausdehnung oder Kontraktion der Schattengeometrie.

Außerdem wird mit dem Parameter shape die Gesamtform des Schattens definiert. Sie können jede Geometrie aus dem androidx.compose.foundation.shape-Paket sowie die Material Expressive-Formen verwenden.

Wenn Sie einen einfachen Schlagschatten implementieren möchten, fügen Sie der zusammensetzbaren Kette den Modifikator dropShadow() hinzu und geben Sie Radius, Farbe und Streuung an. Beachte, dass der Hintergrund purpleColor, der über dem Schatten angezeigt wird, nach dem Modifikator dropShadow() gezeichnet wird:

@Composable
fun SimpleDropShadowUsage() {
    Box(Modifier.fillMaxSize()) {
        Box(
            Modifier
                .width(300.dp)
                .height(300.dp)
                .dropShadow(
                    shape = RoundedCornerShape(20.dp),
                    shadow = Shadow(
                        radius = 10.dp,
                        spread = 6.dp,
                        color = Color(0x40000000),
                        offset = DpOffset(x = 4.dp, 4.dp)
                    )
                )
                .align(Alignment.Center)
                .background(
                    color = Color.White,
                    shape = RoundedCornerShape(20.dp)
                )
        ) {
            Text(
                "Drop Shadow",
                modifier = Modifier.align(Alignment.Center),
                fontSize = 32.sp
            )
        }
    }
}

Wichtige Punkte zum Code

  • Der Modifikator dropShadow() wird auf das innere Box angewendet. Der Schatten hat folgende Eigenschaften:
    • Ein abgerundetes Rechteck (RoundedCornerShape(20.dp))
    • Einen Unschärferadius von 10.dp, wodurch die Ränder weich und diffus werden
    • Eine Streuung von 6.dp, die die Größe des Schattens vergrößert und ihn größer als das Element macht, das ihn wirft
    • Ein Alphawert von 0.5f, wodurch der Schatten halbtransparent wird
  • Nachdem der Schatten definiert wurde, wird die .Der Modifikator „background()“ wird angewendet.
    • Das Box ist weiß ausgefüllt.
    • Der Hintergrund wird auf dieselbe abgerundete Rechteckform wie der Schatten zugeschnitten.

Ergebnis

Ein grauer Schlagschatten um eine weiße rechteckige Form.
Abbildung 2: Ein Schlagschatten, der um die Form gezeichnet wird.

Schatten nach innen implementieren

Wenn Sie einen umgekehrten Effekt zu dropShadow() erstellen möchten, verwenden Sie Modifier.innerShadow(). Dadurch entsteht die Illusion, dass ein Element in die darunter liegende Oberfläche eingelassen oder gedrückt ist.

Die Reihenfolge ist beim Erstellen von inneren Schatten wichtig. Der Modifikator innerShadow() wird über dem Inhalt gezeichnet. Damit der Schatten sichtbar ist, führen Sie normalerweise die folgenden Schritte aus:

  1. Zeichnen Sie den Hintergrundinhalt.
  2. Wenden Sie den Modifikator innerShadow() an, um die konkave Form zu erstellen.

Wenn der innerShadow() vor dem Hintergrund platziert wird, wird der Hintergrund über dem Schatten gezeichnet und verdeckt ihn vollständig.

Das folgende Beispiel zeigt die Anwendung von innerShadow() auf eine RoundedCornerShape:

@Composable
fun SimpleInnerShadowUsage() {
    Box(Modifier.fillMaxSize()) {
        Box(
            Modifier
                .width(300.dp)
                .height(200.dp)
                .align(Alignment.Center)
                // note that the background needs to be defined before defining the inner shadow
                .background(
                    color = Color.White,
                    shape = RoundedCornerShape(20.dp)
                )
                .innerShadow(
                    shape = RoundedCornerShape(20.dp),
                    shadow = Shadow(
                        radius = 10.dp,
                        spread = 2.dp,
                        color = Color(0x40000000),
                        offset = DpOffset(x = 6.dp, 7.dp)
                    )
                )

        ) {
            Text(
                "Inner Shadow",
                modifier = Modifier.align(Alignment.Center),
                fontSize = 32.sp
            )
        }
    }
}

Ein grauer innerer Schatten in einer weißen rechteckigen Form.
Abbildung 3: Anwendung von Modifier.innerShadow() auf ein Rechteck mit abgerundeten Ecken.

Schatten bei Nutzerinteraktion animieren

Wenn Sie möchten, dass Ihre Schatten auf Nutzerinteraktionen reagieren, können Sie Schatteneigenschaften in die Animation APIs von Compose einbinden. Wenn ein Nutzer beispielsweise auf eine Schaltfläche drückt, kann sich der Schatten ändern, um sofortiges visuelles Feedback zu geben.

Mit dem folgenden Code wird ein „Gedrückt“-Effekt mit einem Schatten erstellt (die Illusion, dass die Oberfläche in den Bildschirm gedrückt wird):

@Composable
fun AnimatedColoredShadows() {
    SnippetsTheme {
        Box(Modifier.fillMaxSize()) {
            val interactionSource = remember { MutableInteractionSource() }
            val isPressed by interactionSource.collectIsPressedAsState()

            // Create transition with pressed state
            val transition = updateTransition(
                targetState = isPressed,
                label = "button_press_transition"
            )

            fun <T> buttonPressAnimation() = tween<T>(
                durationMillis = 400,
                easing = EaseInOut
            )

            // Animate all properties using the transition
            val shadowAlpha by transition.animateFloat(
                label = "shadow_alpha",
                transitionSpec = { buttonPressAnimation() }
            ) { pressed ->
                if (pressed) 0f else 1f
            }
            // ...

            val blueDropShadow by transition.animateColor(
                label = "shadow_color",
                transitionSpec = { buttonPressAnimation() }
            ) { pressed ->
                if (pressed) Color.Transparent else blueDropShadowColor
            }

            // ...

            Box(
                Modifier
                    .clickable(
                        interactionSource, indication = null
                    ) {
                        // ** ...... **//
                    }
                    .width(300.dp)
                    .height(200.dp)
                    .align(Alignment.Center)
                    .dropShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 10.dp,
                            spread = 0.dp,
                            color = blueDropShadow,
                            offset = DpOffset(x = 0.dp, -(2).dp),
                            alpha = shadowAlpha
                        )
                    )
                    .dropShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 10.dp,
                            spread = 0.dp,
                            color = darkBlueDropShadow,
                            offset = DpOffset(x = 2.dp, 6.dp),
                            alpha = shadowAlpha
                        )
                    )
                    // note that the background needs to be defined before defining the inner shadow
                    .background(
                        color = Color(0xFFFFFFFF),
                        shape = RoundedCornerShape(70.dp)
                    )
                    .innerShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 8.dp,
                            spread = 4.dp,
                            color = innerShadowColor2,
                            offset = DpOffset(x = 4.dp, 0.dp)
                        )
                    )
                    .innerShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 20.dp,
                            spread = 4.dp,
                            color = innerShadowColor1,
                            offset = DpOffset(x = 4.dp, 0.dp),
                            alpha = innerShadowAlpha
                        )
                    )

            ) {
                Text(
                    "Animated Shadows",
                    // ...
                )
            }
        }
    }
}

Wichtige Punkte zum Code

  • Deklariert die Start- und Endzustände für die Parameter, die beim Drücken mit transition.animateColor und transition.animateFloat animiert werden sollen.
  • Verwendet updateTransition und stellt die ausgewählte targetState (targetState = isPressed) bereit, um zu prüfen, ob alle Animationen synchronisiert sind. Wenn sich isPressed ändert, verwaltet das Übergangsobjekt automatisch die Animation aller untergeordneten Attribute von ihren aktuellen Werten zu den neuen Zielwerten.
  • Definiert die buttonPressAnimation-Spezifikation, die das Timing und die Beschleunigung des Übergangs steuert. Es wird ein tween (kurz für „in-between“) mit einer Dauer von 400 Millisekunden und einer EaseInOut-Kurve angegeben. Das bedeutet, dass die Animation langsam beginnt, in der Mitte beschleunigt und am Ende langsamer wird.
  • Definiert ein Box mit einer Kette von Modifikatorfunktionen, die alle animierten Attribute anwenden, um das visuelle Element zu erstellen, einschließlich der folgenden:
    • .clickable(): Ein Modifikator, der die Box interaktiv macht.
    • .dropShadow(): Zuerst werden zwei äußere Schlagschatten angewendet. Ihre Farb- und Alpha-Eigenschaften sind mit den animierten Werten (blueDropShadow usw.) verknüpft und erzeugen die anfängliche erhöhte Darstellung.
    • .innerShadow(): Zwei Schatten nach innen werden über dem Hintergrund gezeichnet. Ihre Eigenschaften sind mit dem anderen Satz animierter Werte (innerShadowColor1 usw.) verknüpft und erzeugen das eingerückte Erscheinungsbild.

Ergebnis

Abbildung 4. Ein Schatten, der sich beim Drücken durch den Nutzer bewegt.

Schatten mit Farbverlauf erstellen

Schatten sind nicht auf Volltonfarben beschränkt. Die Shadow API akzeptiert einen Brush, mit dem Sie Schatten mit Farbverlauf erstellen können.

Box(
    modifier = Modifier
        .width(240.dp)
        .height(200.dp)
        .dropShadow(
            shape = RoundedCornerShape(70.dp),
            shadow = Shadow(
                radius = 10.dp,
                spread = animatedSpread.dp,
                brush = Brush.sweepGradient(
                    colors
                ),
                offset = DpOffset(x = 0.dp, y = 0.dp),
                alpha = animatedAlpha
            )
        )
        .clip(RoundedCornerShape(70.dp))
        .background(Color(0xEDFFFFFF)),
    contentAlignment = Alignment.Center
) {
    Text(
        text = breathingText,
        color = Color.Black,
        style = MaterialTheme.typography.bodyLarge
    )
}

Wichtige Punkte zum Code

  • dropShadow() fügt einen Schatten hinter dem Feld hinzu.
  • brush = Brush.sweepGradient(colors) färbt den Schatten mit einem Farbverlauf ein, der durch eine Liste vordefinierter colors rotiert und so einen regenbogenähnlichen Effekt erzeugt.

Ergebnis

Sie können einen Pinsel als Schatten verwenden, um einen Farbverlauf dropShadow() mit einer „atmenden“ Animation zu erstellen:

Abbildung 5: Ein animierter Schlagschatten mit Farbverlauf.

Schatten kombinieren

Sie können die Modifikatoren dropShadow() und innerShadow() kombinieren und übereinanderlegen, um verschiedene Effekte zu erzielen. In den folgenden Abschnitten wird gezeigt, wie Sie mit dieser Technik neumorphe, neobrutalistische und realistische Schatten erzeugen.

Neumorphe Schatten erstellen

Neumorphe Schatten zeichnen sich durch ein weiches Erscheinungsbild aus, das sich organisch aus dem Hintergrund ergibt. So erstellen Sie neumorphe Schatten:

  1. Verwenden Sie ein Element, das dieselben Farben wie der Hintergrund hat.
  2. Wenden Sie zwei schwache, entgegengesetzte Schlagschatten an: einen hellen Schatten in einer Ecke und einen dunklen Schatten in der gegenüberliegenden Ecke.

Im folgenden Snippet werden zwei dropShadow()-Modifikatoren übereinandergelegt, um den neumorphen Effekt zu erzielen:

@Composable
fun NeumorphicRaisedButton(
    shape: RoundedCornerShape = RoundedCornerShape(30.dp)
) {
    val bgColor = Color(0xFFe0e0e0)
    val lightShadow = Color(0xFFFFFFFF)
    val darkShadow = Color(0xFFb1b1b1)
    val upperOffset = -10.dp
    val lowerOffset = 10.dp
    val radius = 15.dp
    val spread = 0.dp
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(bgColor)
            .wrapContentSize(Alignment.Center)
            .size(240.dp)
            .dropShadow(
                shape,
                shadow = Shadow(
                    radius = radius,
                    color = lightShadow,
                    spread = spread,
                    offset = DpOffset(upperOffset, upperOffset)
                ),
            )
            .dropShadow(
                shape,
                shadow = Shadow(
                    radius = radius,
                    color = darkShadow,
                    spread = spread,
                    offset = DpOffset(lowerOffset, lowerOffset)
                ),

            )
            .background(bgColor, shape)
    )
}

Ein weißes Rechteck mit neumorphem Effekt vor einem weißen Hintergrund.
Abbildung 6: Ein neumorpher Schatteneffekt.

Neobrutalistische Schatten erstellen

Der neobrutalistische Stil zeichnet sich durch kontrastreiche, blockartige Layouts, leuchtende Farben und dicke Ränder aus. Verwenden Sie dazu einen dropShadow() ohne Unschärfe und mit einem deutlichen Versatz, wie im folgenden Snippet gezeigt:

@Composable
fun NeoBrutalShadows() {
    SnippetsTheme {
        val dropShadowColor = Color(0xFF007AFF)
        val borderColor = Color(0xFFFF2D55)
        Box(Modifier.fillMaxSize()) {
            Box(
                Modifier
                    .width(300.dp)
                    .height(200.dp)
                    .align(Alignment.Center)
                    .dropShadow(
                        shape = RoundedCornerShape(0.dp),
                        shadow = Shadow(
                            radius = 0.dp,
                            spread = 0.dp,
                            color = dropShadowColor,
                            offset = DpOffset(x = 8.dp, 8.dp)
                        )
                    )
                    .border(
                        8.dp, borderColor
                    )
                    .background(
                        color = Color.White,
                        shape = RoundedCornerShape(0.dp)
                    )
            ) {
                Text(
                    "Neobrutal Shadows",
                    modifier = Modifier.align(Alignment.Center),
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

Ein roter Rahmen um ein weißes Rechteck mit einem blauen Schatten auf gelbem Hintergrund.
Abbildung 7. Ein neobrutalistischer Schatteneffekt.

Realistische Schatten erstellen

Realistische Schatten ahmen Schatten in der physischen Welt nach. Sie werden von einer primären Lichtquelle beleuchtet, was sowohl zu einem direkten als auch zu einem diffuseren Schatten führt. Sie können mehrere dropShadow()- und innerShadow()-Instanzen mit unterschiedlichen Attributen stapeln, um realistische Schatteneffekte zu erzeugen, wie im folgenden Snippet gezeigt:

@Composable
fun RealisticShadows() {
    Box(Modifier.fillMaxSize()) {
        val dropShadowColor1 = Color(0xB3000000)
        val dropShadowColor2 = Color(0x66000000)

        val innerShadowColor1 = Color(0xCC000000)
        val innerShadowColor2 = Color(0xFF050505)
        val innerShadowColor3 = Color(0x40FFFFFF)
        val innerShadowColor4 = Color(0x1A050505)
        Box(
            Modifier
                .width(300.dp)
                .height(200.dp)
                .align(Alignment.Center)
                .dropShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 40.dp,
                        spread = 0.dp,
                        color = dropShadowColor1,
                        offset = DpOffset(x = 2.dp, 8.dp)
                    )
                )
                .dropShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 4.dp,
                        spread = 0.dp,
                        color = dropShadowColor2,
                        offset = DpOffset(x = 0.dp, 4.dp)
                    )
                )
                // note that the background needs to be defined before defining the inner shadow
                .background(
                    color = Color.Black,
                    shape = RoundedCornerShape(100.dp)
                )
// //
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 12.dp,
                        spread = 3.dp,
                        color = innerShadowColor1,
                        offset = DpOffset(x = 6.dp, 6.dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 4.dp,
                        spread = 1.dp,
                        color = Color.White,
                        offset = DpOffset(x = 5.dp, 5.dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 12.dp,
                        spread = 5.dp,
                        color = innerShadowColor2,
                        offset = DpOffset(x = (-3).dp, (-12).dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 3.dp,
                        spread = 10.dp,
                        color = innerShadowColor3,
                        offset = DpOffset(x = 0.dp, 0.dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 3.dp,
                        spread = 9.dp,
                        color = innerShadowColor4,
                        offset = DpOffset(x = 1.dp, 1.dp)
                    )
                )

        ) {
            Text(
                "Realistic Shadows",
                modifier = Modifier.align(Alignment.Center),
                fontSize = 24.sp,
                color = Color.White
            )
        }
    }
}

Wichtige Punkte zum Code

  • Es werden zwei verkettete dropShadow()-Modifikatoren mit unterschiedlichen Eigenschaften angewendet, gefolgt von einem background()-Modifikator.
  • Verkettete innerShadow()-Modifikatoren werden angewendet, um den Metalleffekt am Rand der Komponente zu erzeugen.

Ergebnis

Das vorherige Code-Snippet erzeugt Folgendes:

Ein realistischer weißer Schatten um eine schwarze abgerundete Form.
Abbildung 8. Ein realistischer Schatteneffekt.