Schatten in Compose hinzufügen

Schatten heben die Benutzeroberfläche visuell hervor, zeigen Nutzern Interaktivität an 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 zusammensetzbaren Element, das den Material Design-Richtlinien entspricht.
  • Modifier.dropShadow(): Erstellt einen anpassbaren Schatten, der hinter einem zusammensetzbaren Element angezeigt wird, sodass es erhöht aussieht.
  • Modifier.innerShadow(): Erstellt einen Schatten innerhalb der Rahmen eines zusammensetzbaren Elements, sodass es so aussieht, als wäre es in die Oberfläche dahinter gedrückt.

Modifier.shadow() eignet sich zum Erstellen einfacher Schatten, während die Modifikatoren dropShadow() und innerShadow() eine genauere Steuerung und Präzision bei der Schattenwiedergabe bieten.

Auf dieser Seite wird beschrieben, wie Sie jeden dieser Modifikatoren implementieren, einschließlich der Animation von Schatten bei Nutzerinteraktion und der Verkettung der innerShadow() und dropShadow() Modifikatoren, um Farbverlaufsschatten, 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 Schlagschatten wird auf die Form des zusammensetzbaren Elements 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 dropShadow() Modifikator, um einen genauen Schatten hinter Ihren Inhalten zu zeichnen, wodurch das Element erhöht aussieht.

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

  • radius: Definiert die Weichheit und Streuung der Unschärfe.
  • color: Definiert die Farbe des Farbtons.
  • offset: Positioniert die Schattengeometrie entlang der X- und Y-Achse.
  • spread: Steuert die Ausdehnung oder Verkleinerung der Schattengeometrie.

Außerdem definiert der Parameter shape die Gesamtform des Schattens. Er kann jede Geometrie aus dem Paket androidx.compose.foundation.shape sowie die Material Expressive-Formen verwenden.

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

@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 Informationen zum Code

  • Der Modifikator dropShadow() wird auf das innere Box-Element angewendet. Der Schatten hat die folgenden Eigenschaften:
    • Eine abgerundete Rechteckform (RoundedCornerShape(20.dp))
    • Ein Unschärferadius von 10.dp, wodurch die Kanten weich und diffus werden
    • Eine Streuung von 6.dp, die die Größe des Schattens erweitert und ihn größer als das ihn werfende Feld macht
    • Ein Alpha von 0.5f, wodurch der Schatten halbtransparent wird
  • Nachdem der Schatten definiert wurde, wird der Modifikatorbackground() angewendet.
    • Das Box-Element ist weiß.
    • 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 wurde.

Innere Schatten implementieren

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

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

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

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

Im folgenden Beispiel wird innerShadow() auf eine RoundedCornerShape angewendet:

@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 einem weißen Rechteck.
Abbildung 3. Eine Anwendung von Modifier.innerShadow() auf ein abgerundetes Rechteck.

Schatten bei Nutzerinteraktion animieren

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

Mit dem folgenden Code wird ein „gedrückter“ 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 Informationen 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 den ausgewählten 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 Eigenschaften von ihren aktuellen Werten zu den neuen Zielwerten.
  • Definiert die Spezifikation buttonPressAnimation, die das Timing und das Easing 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 schneller wird und am Ende wieder langsamer wird.
  • Definiert ein Box-Element mit einer Kette von Modifikatorfunktionen, die alle animierten Eigenschaften anwenden, um das visuelle Element zu erstellen, einschließlich:
    • .clickable(): Ein Modifikator, der das 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 das anfängliche erhöhte Aussehen.
    • .innerShadow(): Zwei innere Schatten werden über dem Hintergrund gezeichnet. Ihre Eigenschaften sind mit den anderen animierten Werten (innerShadowColor1 usw.) verknüpft und erzeugen das vertiefte Aussehen.

Ergebnis

Abbildung 4. Ein Schatten, der bei Nutzerinteraktion animiert wird.

Farbverlaufsschatten erstellen

Schatten sind nicht auf Volltonfarben beschränkt. Die Schatten-API akzeptiert einen Brush, mit dem Sie Farbverlaufsschatten 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 Informationen zum Code

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

Ergebnis

Sie können einen Pinsel als Schatten verwenden, um einen dropShadow()-Farbverlauf 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 überlagern, um eine Vielzahl von Effekten zu erzielen. In den folgenden Abschnitten wird gezeigt, wie Sie mit dieser Technik neumorphe, neobrutalistische und realistische Schatten erstellen.

Neumorphe Schatten erstellen

Neumorphe Schatten zeichnen sich durch ein weiches Aussehen aus, das organisch aus dem Hintergrund entsteht. 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 überlagert, 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 einem neumorphen Effekt vor einem weißen Hintergrund.
Abbildung 6. Ein neumorpher Schatteneffekt.

Neobrutalistische Schatten erstellen

Der neobrutalistische Stil zeichnet sich durch kontrastreiche, blockartige Layouts, lebendige Farben und dicke Rahmen aus. Um diesen Effekt zu erzielen, verwenden Sie einen dropShadow()-Modifikator ohne Unschärfe und mit einem deutlichen Offset, 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 weißes Rechteck mit einem blauen Schatten und einem roten Rahmen vor einem gelben Hintergrund.
Abbildung 7. Ein neobrutalistischer Schatteneffekt.

Realistische Schatten erstellen

Realistische Schatten ahmen Schatten in der physischen Welt nach. Sie sehen so aus, als würden sie von einer primären Lichtquelle beleuchtet, was zu einem direkten und einem diffuseren Schatten führt. Sie können mehrere dropShadow()- und innerShadow()-Instanzen mit unterschiedlichen Eigenschaften 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 Informationen zum Code

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

Ergebnis

Das vorherige Code-Snippet erzeugt Folgendes:

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