Cienie wizualnie uatrakcyjniają interfejs, wskazują użytkownikom interaktywność i zapewniają natychmiastowe informacje zwrotne o działaniach użytkownika. Compose udostępnia kilka sposobów na dodanie cieni do aplikacji:
Modifier.shadow(): tworzy cień oparty na wysokości za elementem kompozycyjnym, który jest zgodny ze wskazówkami Material Design.Modifier.dropShadow(): tworzy cień, który można dostosować i który pojawia się za elementem kompozycyjnym, dzięki czemu wydaje się on uniesiony.Modifier.innerShadow(): tworzy cień wewnątrz obramowania elementu kompozycyjnego, dzięki czemu wydaje się on wciśnięty w powierzchnię za nim.
Modyfikator Modifier.shadow() nadaje się do tworzenia podstawowych cieni, a modyfikatory dropShadow() i innerShadow() zapewniają bardziej szczegółową kontrolę i precyzję renderowania cieni.
Na tej stronie opisujemy, jak zaimplementować każdy z tych modyfikatorów, w tym jak animować cienie po interakcji użytkownika oraz jak połączyć modyfikatory innerShadow() i dropShadow(), aby tworzyć cienie gradientowe, cienie neumorficzne i inne.
Tworzenie podstawowych cieni
Modifier.shadow() tworzy podstawowy cień zgodnie ze wskazówkami Material Design, który symuluje źródło światła nad obiektem. Głębokość cienia zależy od wartości elevation, a rzucany cień jest przycinany do kształtu elementu kompozycyjnego.
@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) ) } }
Modifier.shadow().Zastosuj cienie.
Użyj modyfikatora dropShadow(), aby narysować dokładny cień za treścią, dzięki czemu element będzie wyglądał na uniesiony.
Za pomocą parametru Shadow możesz kontrolować te kluczowe aspekty:
radius: określa miękkość i rozproszenie rozmycia.color: określa kolor odcienia.offset: określa położenie geometrii cienia wzdłuż osi X i Y.spread: określa rozszerzanie lub zmniejszanie geometrii cienia.
Dodatkowo parametr shape określa ogólny kształt cienia. Może używać dowolnej geometrii z pakietu androidx.compose.foundation.shape, a także kształtów Material Expressive.
Aby zaimplementować podstawowy cień, dodaj modyfikator dropShadow() do łańcucha funkcji kompozycyjnych, podając promień, kolor i rozprzestrzenianie. Zwróć uwagę, że tło purpleColor, które pojawia się nad cieniem, jest rysowane po modyfikatorze dropShadow():
@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 ) } } }
Najważniejsze informacje o kodzie
- Modyfikator
dropShadow()jest stosowany do wewnętrznego elementuBox. Cień ma te cechy:- Zaokrąglony prostokąt (
RoundedCornerShape(20.dp)) - promień rozmycia
10.dp, dzięki czemu krawędzie są miękkie i rozproszone; - rozprzestrzenianie
6.dp, które zwiększa rozmiar cienia i sprawia, że jest on większy niż pole, które go rzuca; - wartość alfa
0.5f, dzięki czemu cień jest półprzezroczysty;
- Zaokrąglony prostokąt (
- Po zdefiniowaniu cienia,Zastosowano modyfikator
background().Boxjest wypełniony białym kolorem.- Tło jest przycięte do tego samego kształtu zaokrąglonego prostokąta co cień.
Wynik
Implementowanie cieni wewnętrznych
Aby utworzyć efekt odwrotny do dropShadow(), użyj Modifier.innerShadow(), który tworzy iluzję, że element jest zagłębiony lub wciśnięty w powierzchnię pod nim.
Kolejność jest ważna podczas tworzenia wewnętrznych cieni. Modyfikator innerShadow() jest rysowany na treści. Aby upewnić się, że cień jest widoczny, zwykle wykonujesz te czynności:
- Narysuj zawartość tła.
- Aby uzyskać efekt wklęsłości, zastosuj modyfikator
innerShadow().
Jeśli element innerShadow() zostanie umieszczony przed tłem, tło zostanie narysowane nad cieniem, całkowicie go ukrywając.
Poniższy przykład pokazuje zastosowanie elementu innerShadow() w przypadku 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 ) } } }
Modifier.innerShadow() na prostokącie z zaokrąglonymi rogami.Animuj cienie podczas interakcji użytkownika
Aby cienie reagowały na interakcje użytkownika, możesz zintegrować właściwości cieni z interfejsami API animacji Compose. Gdy użytkownik naciśnie przycisk, cień może się zmienić, aby natychmiast przekazać wizualną informację zwrotną.
Ten kod tworzy efekt „naciśnięcia” z cieniem (złudzenie, że powierzchnia jest wciskana w ekran):
@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", // ... ) } } } }
Najważniejsze informacje o kodzie
- Deklaruje stany początkowe i końcowe parametrów, które mają być animowane po naciśnięciu, za pomocą
transition.animateColoritransition.animateFloat. - Używa
updateTransitioni udostępnia mu wybraną wartośćtargetState (targetState = isPressed), aby sprawdzić, czy wszystkie animacje są zsynchronizowane. Za każdym razem, gdy zmienia sięisPressed, obiekt przejścia automatycznie zarządza animacją wszystkich właściwości podrzędnych od ich bieżących wartości do nowych wartości docelowych. - Definiuje specyfikację
buttonPressAnimation, która kontroluje czas i płynność przejścia. Określatween(skrót od „in-between”, czyli „pomiędzy”) o czasie trwania 400 milisekund i krzywąEaseInOut, co oznacza, że animacja zaczyna się powoli, przyspiesza w środku i zwalnia na końcu. - Definiuje
Boxz łańcuchem funkcji modyfikujących, które stosują wszystkie animowane właściwości, aby utworzyć element wizualny, w tym:- .
clickable(): modyfikator, który sprawia, że elementBoxjest interaktywny. .dropShadow(): najpierw stosowane są 2 zewnętrzne cienie. Ich właściwości koloru i alfa są powiązane z animowanymi wartościami (blueDropShadowitd.) i tworzą początkowy efekt podniesienia..innerShadow(): na tle są rysowane 2 cienie wewnętrzne. Ich właściwości są połączone z innym zestawem animowanych wartości (innerShadowColor1itd.) i tworzą wygląd z wcięciem.
- .
Wynik
Tworzenie cieni gradientowych
Cienie nie ograniczają się do jednolitych kolorów. Interfejs API cieni akceptuje Brush, co umożliwia tworzenie cieni gradientowych.
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 ) }
Najważniejsze informacje o kodzie
dropShadow()dodaje cień za polem.brush = Brush.sweepGradient(colors)nadaje cieniowi kolor gradientu, który przechodzi przez listę wstępnie zdefiniowanychcolors, tworząc efekt tęczy.
Wynik
Możesz użyć pędzla jako cienia, aby utworzyć gradient dropShadow() z animacją „oddychania”:
Łączenie cieni
Możesz łączyć i nakładać modyfikatory dropShadow() i innerShadow(), aby tworzyć różne efekty. Z sekcji poniżej dowiesz się, jak za pomocą tej techniki tworzyć neumorficzne, neobrutalistyczne i realistyczne cienie.
Tworzenie neumorficznych cieni
Cienie neumorficzne charakteryzują się miękkim wyglądem, który naturalnie wyłania się z tła. Aby utworzyć neumorficzne cienie:
- używać elementu, który ma takie same kolory jak tło;
- Zastosuj 2 delikatne, przeciwstawne cienie: jasny w jednym rogu i ciemny w przeciwnym.
Poniższy fragment kodu nakłada na siebie 2 modyfikatory dropShadow(), aby utworzyć efekt neumorficzny:
@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) ) }
Tworzenie neobrutalistycznych cieni
Styl neobrutalistyczny charakteryzuje się wysokim kontrastem, blokowymi układami, żywymi kolorami i grubymi obramowaniami. Aby uzyskać ten efekt, użyj dropShadow() z zerowym rozmyciem i wyraźnym przesunięciem, jak pokazano w tym fragmencie kodu:
@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 ) } } } }
Tworzenie realistycznych cieni
Realistyczne cienie naśladują cienie w świecie fizycznym – wyglądają, jakby były oświetlone przez główne źródło światła, co daje zarówno bezpośredni, jak i bardziej rozproszony cień. Możesz ułożyć kilka instancji dropShadow() i innerShadow() z różnymi właściwościami, aby odtworzyć realistyczne efekty cienia, jak pokazano w tym fragmencie kodu:
@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 ) } } }
Najważniejsze informacje o kodzie
- Zastosowano 2 połączone modyfikatory
dropShadow()z różnymi właściwościami, a następnie modyfikatorbackground(). - Aby uzyskać efekt metalicznej krawędzi wokół komponentu, zastosowano połączone modyfikatory
innerShadow().
Wynik
Poprzedni fragment kodu generuje te dane: