Cienie wizualnie podnoszą jakość interfejsu, wskazują użytkownikom interaktywność i zapewniają natychmiastową informację zwrotną o działaniach użytkownika. Compose udostępnia kilka sposobów na dodanie cieni do aplikacji:
Modifier.shadow(): tworzy cień za elementem kompozycyjnym na podstawie wysokości, zgodnie z wytycznymi Material Design.Modifier.dropShadow(): tworzy konfigurowalny cień, który pojawia się za elementem kompozycyjnym, dzięki czemu wygląda on na podniesiony.Modifier.innerShadow(): tworzy cień wewnątrz obramowania elementu kompozycyjnego, dzięki czemu wygląda on na wciśnięty w powierzchnię za nim.
Modifier.shadow() nadaje się do tworzenia podstawowych cieni, a modyfikatory dropShadow() i innerShadow() zapewniają większą kontrolę i precyzję renderowania cieni.
Na tej stronie opisujemy, jak zaimplementować każdy z tych modyfikatorów, w tym jak
animować cienie w reakcji na interakcję użytkownika oraz jak łączyć modyfikatory
innerShadow() i dropShadow() w celu
tworzenia cieni gradientowych, cieni neumorficznych i innych.
Tworzenie podstawowych cieni
Modifier.shadow() tworzy podstawowy cień zgodny z wytycznymi Material Design, który symuluje źródło światła z góry. Głębokość cienia zależy od wartości elevation, a cień rzucany 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().Implementowanie cieni rzucanych
Użyj modyfikatora dropShadow(), aby narysować dokładny cień za treścią
, dzięki czemu element będzie wyglądał na podniesiony.
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: kontroluje rozszerzanie lub kurczenie się geometrii cienia.
Dodatkowo parametr shape określa ogólny kształt cienia. Może on
używać dowolnej geometrii z pakietu androidx.compose.foundation.shape, a także
kształtów Material Expressive.
Aby zaimplementować podstawowy cień rzucany, dodaj modyfikator dropShadow() do łańcucha elementów kompozycyjnych, podając promień, kolor i rozproszenie. Pamiętaj, że tło purpleColor widoczne nad cieniem jest rysowane po zastosowaniu modyfikatora 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:- Kształt zaokrąglonego prostokąta (
RoundedCornerShape(20.dp)) - Promień rozmycia
10.dp, dzięki czemu krawędzie są miękkie i rozproszone - Rozproszenie
6.dp, które powiększa cień i sprawia, że jest on większy niż rzucający go element Box - Alfa
0.5f, dzięki czemu cień jest półprzezroczysty
- Kształt zaokrąglonego prostokąta (
- Po zdefiniowaniu cienia stosowany jest modyfikator
background()modyfikator jest zastosowany.- Element
Boxjest wypełniony białym kolorem. - Tło jest przycinane do tego samego kształtu zaokrąglonego prostokąta co cień.
- Element
Wynik
Implementowanie cieni wewnętrznych
Aby utworzyć efekt odwrotny do dropShadow(), use
Modifier.innerShadow(), który tworzy iluzję, że element jest
wpuszczony lub wciśnięty w powierzchnię pod nim.
Podczas tworzenia cieni wewnętrznych kolejność ma znaczenie. Modyfikator innerShadow() rysuje się nad treścią. Aby cień był widoczny, zwykle wykonujesz te czynności:
- Narysuj zawartość tła.
- Zastosuj modyfikator
innerShadow(), aby utworzyć wklęsły wygląd.
Jeśli innerShadow() zostanie umieszczony przed tłem, tło zostanie narysowane nad cieniem, całkowicie go zasłaniając.
Ten przykład pokazuje zastosowanie innerShadow() na 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.Animowanie cieni w reakcji na interakcję użytkownika
Aby cienie reagowały na interakcje użytkownika, możesz zintegrować właściwości cienia z interfejsami API animacji Compose. Gdy użytkownik naciśnie np. przycisk, cień może się zmienić, aby zapewnić natychmiastową informację zwrotną.
Ten kod tworzy efekt „naciśnięcia” z cieniem (iluzja, że powierzchnia jest wciśnięta 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ątkowy i końcowy parametrów, które mają być animowane po naciśnięciu, za pomocą
transition.animateColoritransition.animateFloat. - Używa
updateTransitioni podaje wybranytargetState (targetState = isPressed), aby sprawdzić, czy wszystkie animacje są zsynchronizowane. GdyisPressedsię zmieni, 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 wygładzanie przejścia. Określatween(skrót od in-between) o czasie trwania 400 milisekund i krzywejEaseInOut, co oznacza, że animacja zaczyna się powoli, przyspiesza w środku i zwalnia na końcu. - Definiuje element
Boxz łańcuchem funkcji modyfikatora, 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 rzucane. Ich właściwości koloru i alfa są powiązane z animowanymi wartościami (blueDropShadowitp.) i tworzą początkowy podniesiony wygląd..innerShadow(): 2 cienie wewnętrzne są rysowane nad tłem. Ich właściwości są powiązane z innym zestawem animowanych wartości (innerShadowColor1itp.) i tworzą wcięty wygląd.
- .
Wynik
Tworzenie cieni gradientowych
Cienie nie muszą być jednolite. Interfejs API cieni akceptuje Brush, który 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 elementem Box.brush = Brush.sweepGradient(colors)koloruje cień gradientem, który obraca się wokół listy predefiniowanychcolors, tworząc efekt tęczy.
Wynik
Możesz użyć pędzla jako cienia, aby utworzyć gradient dropShadow() z animacją „oddechu”:
Łączenie cieni
Możesz łączyć i nakładać na siebie modyfikatory dropShadow() i innerShadow(), aby tworzyć różne efekty. Z sekcji poniżej dowiesz się, jak za pomocą tej techniki tworzyć cienie neumorficzne, neobrutalistyczne i realistyczne.
Tworzenie cieni neumorficznych
Cienie neumorficzne charakteryzują się miękkim wyglądem, który naturalnie wyłania się z tła. Aby utworzyć cienie neumorficzne:
- Użyj elementu, który ma te same kolory co tło.
- Zastosuj 2 słabe, przeciwstawne cienie rzucane: jasny cień w jednym rogu i ciemny cień w rogu przeciwnym.
Ten 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 cieni neobrutalistycznych
Styl neobrutalistyczny charakteryzuje się układami blokowymi o wysokim kontraście, żywymi kolorami i grubymi obramowaniami. Aby utworzyć 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 cieni realistycznych
Cienie realistyczne naśladują cienie w świecie fizycznym – wyglądają tak, jakby były oświetlone przez główne źródło światła, co powoduje powstanie zarówno cienia bezpośredniego, jak i bardziej rozproszonego. Aby odtworzyć realistyczne efekty cienia, możesz ułożyć w stos kilka instancji dropShadow() i innerShadow() z różnymi właściwościami, 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
- Stosowane są 2 połączone modyfikatory
dropShadow()z różnymi właściwościami, a następnie modyfikatorbackground(). - Stosowane są połączone modyfikatory
innerShadow(), aby utworzyć efekt metalowej krawędzi wokół krawędzi komponentu.
Wynik
Poprzedni fragment kodu daje ten efekt: