Тени визуально улучшают пользовательский интерфейс, указывают на интерактивность для пользователей и обеспечивают мгновенную обратную связь о действиях пользователя. Compose предоставляет несколько способов внедрения теней в ваше приложение:
-
Modifier.shadow(): Создает тень, зависящую от высоты, за составным элементом, соответствующим рекомендациям Material Design. -
Modifier.dropShadow(): Создает настраиваемую тень, которая появляется за составным элементом, придавая ему приподнятый вид. -
Modifier.innerShadow(): Создает тень внутри границ составного объекта, из-за чего он кажется вдавленным в поверхность позади него.
Modifier.shadow() подходит для создания простых теней, в то время как модификаторы dropShadow() и innerShadow() обеспечивают более точный контроль и управление при отрисовке теней.
На этой странице описано, как реализовать каждый из этих модификаторов, включая анимацию теней при взаимодействии с пользователем и как объединить модификаторы innerShadow() и dropShadow() для создания градиентных теней , неоморфных теней и многого другого.
Создание базовых теней
Modifier.shadow() создает базовую тень в соответствии с принципами Material Design , имитирующую источник света сверху. Глубина тени зависит от значения elevation , а отбрасываемая тень обрезается по форме объекта композиции.
@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 Expressive .
Для реализации простой тени добавьте модификатор dropShadow() к вашей цепочке композиционных элементов, указав радиус, цвет и ширину. Обратите внимание, что фон 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
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() к прямоугольнику со скругленными углами.Анимировать тени при взаимодействии с пользователем
Чтобы тени реагировали на действия пользователя, вы можете интегрировать свойства теней с 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) длительностью 400 миллисекунд и кривуюEaseInOut, что означает, что анимация начинается медленно, ускоряется в середине и замедляется в конце. - Определяет
Boxс цепочкой функций-модификаторов, которые применяют все анимированные свойства для создания визуального элемента, включая следующие:-
clickable(): Модификатор, делающийBoxинтерактивным. -
.dropShadow(): Сначала применяются две внешние тени. Их цвет и альфа-канал связаны с анимированными значениями (blueDropShadowи т. д.) и создают первоначальный эффект приподнятости. -
.innerShadow(): На фоне рисуются две внутренние тени. Их свойства связаны с другим набором анимированных значений (innerShadowColor1и т. д.) и создают эффект отступа.
-
Результат
Создание градиентных теней
Тени не ограничиваются сплошными цветами. 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().
Результат
Предыдущий фрагмент кода выдает следующий результат:
