Тени визуально приподнимают элементы интерфейса, указывают на их интерактивность и обеспечивают немедленную обратную связь при действиях пользователя. Compose предоставляет несколько способов включения теней в ваше приложение:
Modifier.shadow()создает тень за composable-функцией, которая соответствует рекомендациям Material Design.Modifier.dropShadow()создает настраиваемую тень за composable-функцией, чтобы она выглядела приподнятой.Modifier.innerShadow()создает тень внутри границ composable-функции, чтобы она выглядела вдавленной в поверхность за ней.
Modifier.shadow() подходит для создания простых теней, а модификаторы dropShadow() и innerShadow() позволяют точнее настраивать отрисовку теней.
На этой странице описывается, как реализовать каждый из этих модификаторов, в том числе как анимировать тени при взаимодействии с пользователем и как связать модификаторы innerShadow() и dropShadow() для создания градиентных теней, неоморфических теней и многого другого.
Создать простые тени
Modifier.shadow() создает базовую тень в соответствии с рекомендациями Material Design, имитируя верхний источник света. Глубина тени зависит от значения elevation, а отбрасываемая тень обрезается по форме composable-функции.
@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().Добавьте тени
Используйте модификатор dropShadow(), чтобы нарисовать точную тень за вашим контентом, благодаря чему элемент будет выглядеть приподнятым.
С помощью параметра Shadow можно управлять следующими ключевыми аспектами:
radius– определяет мягкость и рассеивание размытия.colorопределяет цвет оттенка.offset– позиционирует геометрию тени по осям X и Y.spread– управляет расширением или сжатием геометрии тени.
Кроме того, параметр shape определяет общую форму тени. Можно использовать любую геометрию из пакета androidx.compose.foundation.shape, а также выразительные фигуры Material.
Чтобы добавить простую тень, добавьте модификатор dropShadow() в цепочку composable-функций, указав радиус, цвет и распространение. Обратите внимание, что фон purpleColor, который появляется поверх тени, рисуется после модификатора 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 ) } } }
Важные сведения о коде
- Модификатор
dropShadow()применяется к внутреннемуBox. Тень имеет следующие характеристики:- Прямоугольник со скругленными углами (
RoundedCornerShape(20.dp)) - Радиус размытия
10.dp, чтобы края были мягкими и размытыми. - Размытие
6.dp, которое увеличивает размер тени, делая ее больше, чем отбрасывающий ее блок. - Альфа-канал
0.5f, который делает тень полупрозрачной.
- Прямоугольник со скругленными углами (
- После определения тени,Применен
background()модификатор.Boxзаполнена белым цветом.- Фон обрезан по той же форме прямоугольника со скругленными углами, что и тень.
Результат
Как реализовать внутренние тени
Чтобы создать эффект, противоположный dropShadow(), используйте Modifier.innerShadow(). Это создаст иллюзию того, что элемент утоплен или вдавлен в поверхность.
При создании внутренних теней порядок имеет значение. Модификатор innerShadow() рисует поверх контента. Чтобы убедиться, что тень видна, вы обычно
выполняете следующие шаги:
- Нарисуйте фон.
- Примените модификатор
innerShadow(), чтобы создать вогнутую поверхность.
Если innerShadow() разместить перед фоном, фон будет нарисован поверх тени и полностью ее скроет.
Ниже показано, как применить innerShadow() к 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() к прямоугольнику со скругленными углами.Анимировать тени при взаимодействии с пользователем
Чтобы тени реагировали на действия пользователя, можно интегрировать их свойства с Animation API в Compose. Например, когда пользователь нажимает кнопку, тень может измениться, чтобы обеспечить мгновенную визуальную обратную связь.
Следующий код создает эффект нажатия с тенью (иллюзию того, что поверхность вдавливается в экран):
@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", // ... ) } } } }
Важные сведения о коде
- Объявляет начальное и конечное состояния для параметров, которые нужно анимировать при нажатии, с помощью
transition.animateColorиtransition.animateFloat. - Использует
updateTransitionи предоставляет ему выбранныйtargetState (targetState = isPressed), чтобы убедиться, что все анимации синхронизированы. При измененииisPressedобъект перехода автоматически управляет анимацией всех дочерних свойств от их текущих значений до новых целевых значений. - Определяет спецификацию
buttonPressAnimation, которая управляет временем и плавностью перехода. Оно задаетtween(сокращение от in-between) с длительностью 400 миллисекунд и кривойEaseInOut, что означает, что анимация начинается медленно, ускоряется в середине и замедляется в конце. - Определяет
Boxс цепочкой функций-модификаторов, которые применяют все анимированные свойства для создания визуального элемента, включая следующие:- .
clickable()– модификатор, который делаетBoxинтерактивным. .dropShadow(): сначала применяются две внешние тени. Их свойства цвета и альфа-канала связаны с анимированными значениями (blueDropShadowи т. д.) и создают начальный приподнятый вид..innerShadow()– поверх фона нарисованы две внутренние тени. Их свойства связаны с другим набором анимированных значений (innerShadowColor1и т. д.) и создают эффект отступа.
- .
Результат
Создание градиентных теней
Тени не ограничиваются сплошными цветами. Shadow API принимает Brush, что позволяет создавать градиентные тени.
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 ) }
Важные сведения о коде
dropShadow()добавляет тень за полем.brush = Brush.sweepGradient(colors)окрашивает тень градиентом, который циклически меняет цвета из спискаcolors, создавая эффект радуги.
Результат
Вы можете использовать кисть в качестве тени, чтобы создать градиент dropShadow() с анимацией "дыхания":
Объединение теней
Вы можете комбинировать модификаторы dropShadow() и innerShadow(), чтобы создавать различные эффекты. В следующих разделах показано, как с помощью этой техники создавать нейморфические, нео-бруталистические и реалистичные тени.
Создать тени в стиле неоморфизма
Тени в неоморфизме мягкие и органично выходят из фона. Чтобы создать неоморфические тени, выполните следующие действия:
- Использовать элемент, цвет которого совпадает с цветом фона.
- Примените две слабые противоположные тени: светлую тень в одном углу и темную тень в противоположном углу.
В следующем фрагменте кода два модификатора dropShadow() накладываются друг на друга, чтобы создать эффект неоморфизма:
@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) ) }
Создать тени в стиле необрутализма
Необрутализм – это стиль с высокой контрастностью, блочными макетами, яркими цветами и толстыми границами. Чтобы создать такой эффект, используйте dropShadow() с нулевым размытием и заметным смещением, как показано в следующем фрагменте кода:
@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 ) } } } }
Создавайте реалистичные тени
Реалистичные тени имитируют тени в физическом мире – они кажутся освещенными основным источником света, в результате чего получается как прямая тень, так и более рассеянная тень. Вы можете накладывать друг на друга несколько экземпляров dropShadow() и innerShadow() с разными свойствами, чтобы воссоздать реалистичные эффекты тени, как показано в следующем фрагменте:
@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 ) } } }
Важные сведения о коде
- Применяются два связанных модификатора
dropShadow()с разными свойствами, а затем модификаторbackground(). - Для создания эффекта металлического ободка по краю компонента применяется цепочка модификаторов
innerShadow().
Результат
Приведенный выше фрагмент кода создает следующее: