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 schattenbasierten Schatten hinter einem Composable, der den Material Design-Richtlinien entspricht.
  • Modifier.dropShadow(): Erstellt einen anpassbaren Schatten, der hinter einem Composable angezeigt wird, sodass es erhöht erscheint.
  • Modifier.innerShadow(): Erstellt einen Schatten innerhalb der Ränder eines Composables, sodass es aussieht, als wäre es in die Oberfläche dahinter eingedrückt.

Modifier.shadow() eignet sich zum Erstellen einfacher Schatten, während die Modifizierer dropShadow und innerShadow eine detailliertere Steuerung und Präzision beim Rendern von Schatten ermöglichen.

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

Einfache Schatten erstellen

Mit Modifier.shadow() wird ein einfacher Schatten gemäß den Material Design-Richtlinien erstellt, der eine Lichtquelle von oben simuliert. Die Schattenstärke basiert auf einem elevation-Wert und der Schatten wird auf die Form des Composables 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 ein weißes Rechteck.
Abbildung 1: Ein höhenbasierter Schatten, der mit Modifier.shadow erstellt wurde

Schlagschatten implementieren

Mit dem Modifikator dropShadow() können Sie einen präzisen Schatten hinter Ihren Inhalten zeichnen, wodurch das Element erhöht erscheint.

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

  • radius: Definiert die Weichheit und Streuung des Weichzeichners.
  • color: Definiert die Farbe des Tints.
  • offset: Positioniert die Geometrie des Schattens entlang der x- und y-Achse.
  • spread: Steuert die Vergrößerung oder Verkleinerung der Schattengeometrie.

Außerdem wird mit dem Parameter shape die Gesamtform des Schattens definiert. Es kann jede Geometrie aus dem androidx.compose.foundation.shape-Paket sowie die Material Expressive shapes verwenden.

Wenn Sie einen einfachen Schlagschatten implementieren möchten, fügen Sie den dropShadow()-Modifikator in Ihre zusammensetzbare Kette ein und geben Sie Radius, Farbe und Streuung an. Beachten Sie, dass der purpleColor-Hintergrund, der über dem Schatten angezeigt wird, nach dem dropShadow()-Modifikator 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 die innere Box angewendet. Der Schatten hat die folgenden Eigenschaften:
    • Ein abgerundetes Rechteck (RoundedCornerShape(20.dp))
    • Ein Weichzeichnungsradius von 10.dp, wodurch die Kanten weich und diffus werden
    • Ein „Spread“-Wert von 6.dp vergrößert den Schatten und macht ihn größer als den Rahmen, der ihn wirft.
    • Ein Alpha von 0.5f, wodurch der Schatten halbtransparent wird
  • Nachdem der Schatten definiert wurde,Der Modifikator „background()“ wird angewendet.
    • Die 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 wird um die Form gezeichnet.

Schatten nach innen implementieren

Wenn Sie einen umgekehrten Effekt zu dropShadow erzielen möchten, verwenden Sie Modifier.innerShadow(). Dadurch wird die Illusion erzeugt, dass ein Element in die darunterliegende Oberfläche eingelassen oder eingedrückt ist.

Die Reihenfolge ist beim Erstellen von inneren Schatten wichtig. Der innere Schatten wird über dem Inhalt gezeichnet. Normalerweise gehen Sie so vor:

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

Wenn 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 ein 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 einem weißen Rechteck.
Abbildung 3. Anwendung von Modifier.innerShadow() auf ein Rechteck mit abgerundeten Ecken.

Schatten bei Nutzerinteraktion animieren

Damit Schatten auf Nutzerinteraktionen reagieren, können Sie Schattenattribute in die Animations-APIs von Compose einbinden. Wenn ein Nutzer beispielsweise eine Schaltfläche drückt, 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 Punkte zum Code

  • Deklariert die Start- und Endstatus für die Parameter, die beim Drücken animiert werden sollen, mit transition.animateColor und transition.animateFloat.
  • Verwendet updateTransition und stellt die ausgewählte targetState (targetState = isPressed) bereit, um zu prüfen, ob alle Animationen synchronisiert sind. Immer 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 schneller wird und am Ende wieder langsamer wird.
  • Definiert ein Box mit einer Kette von Modifikatorfunktionen, die alle animierten Eigenschaften anwenden, um das visuelle Element zu erstellen, einschließlich der folgenden:
    • .clickable(): Ein Modifikator, der das Box interaktiv macht.
    • .dropShadow(): Zuerst werden zwei äußere Schlagschatten angewendet. Die Farb- und Alphaproperties sind mit den animierten Werten (blueDropShadow usw.) verknüpft und sorgen für die anfängliche Erhebung.
    • .innerShadow(): Zwei Schatten nach innen werden über dem Hintergrund gezeichnet. Ihre Eigenschaften sind mit den anderen animierten Werten (innerShadowColor1 usw.) verknüpft und sorgen für das eingerückte Erscheinungsbild.

Ergebnis

Abbildung 4. Ein Schatten wird animiert, wenn der Nutzer darauf drückt.

Schatten mit Farbverlauf erstellen

Schatten sind nicht auf Volltonfarben beschränkt. Die Schatten-API akzeptiert ein Brush, mit dem Sie Verlaufschatten 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

  • Mit dropShadow() wird ein Schatten hinter dem Feld hinzugefügt.
  • 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 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 sein Hintergrund hat.
  2. Wenden Sie zwei schwache, gegenüberliegende Schlagschatten an: einen hellen Schatten in einer Ecke und einen dunklen Schatten in der gegenüberliegenden Ecke.

Im folgenden Snippet werden zwei dropShadow()-Modifikatoren geschichtet, 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 Ränder aus. Verwenden Sie für diesen Effekt einen dropShadow() 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 roter Rahmen um ein weißes Rechteck mit einem blauen Schatten vor einem gelben Hintergrund.
Abbildung 7. Ein neobrutalistischer Schatteneffekt.

Realistische Schatten erstellen

Realistische Schatten ähneln Schatten in der physischen Welt. Sie werden von einer primären Lichtquelle beleuchtet, was zu einem direkten und einem diffusen Schatten führt. Sie können mehrere dropShadow()- und innerShadow()-Instanzen mit unterschiedlichen Eigenschaften stapeln, um realistische Schatteneffekte zu erzielen, 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()-Modifizierer werden angewendet, um den metallischen Rändereffekt 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.