Compose имеет множество встроенных механизмов анимации, и выбор одного из них может быть сложным. Ниже представлен список распространённых вариантов использования анимации. Более подробную информацию о полном наборе доступных вам вариантов API см. в полной документации по Compose Animation .
Анимировать общие компонуемые свойства
Compose предоставляет удобные API, позволяющие решать множество распространённых задач анимации. В этом разделе показано, как анимировать распространённые свойства компонуемого объекта.
Анимированное появление/исчезновение

Используйте AnimatedVisibility
, чтобы скрыть или отобразить Composable. Дочерние элементы внутри AnimatedVisibility
могут использовать Modifier.animateEnterExit()
для собственных переходов входа и выхода.
var visible by remember { mutableStateOf(true) } // Animated visibility will eventually remove the item from the composition once the animation has finished. AnimatedVisibility(visible) { // your composable here // ... }
Параметры входа и выхода AnimatedVisibility
позволяют настроить поведение компонуемого объекта при появлении и исчезновении. Подробнее см. в полной документации .
Другой вариант анимации видимости составного объекта — анимация альфы с течением времени с помощью animateFloatAsState
:
var visible by remember { mutableStateOf(true) } val animatedAlpha by animateFloatAsState( targetValue = if (visible) 1.0f else 0f, label = "alpha" ) Box( modifier = Modifier .size(200.dp) .graphicsLayer { alpha = animatedAlpha } .clip(RoundedCornerShape(8.dp)) .background(colorGreen) .align(Alignment.TopCenter) ) { }
Однако изменение альфа-канала подразумевает, что компонуемый элемент остаётся в композиции и продолжает занимать пространство, в котором он размещён. Это может привести к тому, что программы чтения с экрана и другие механизмы обеспечения доступности по-прежнему будут считать элемент на экране. С другой стороны, AnimatedVisibility
в конечном итоге удаляет элемент из композиции.

Анимированный цвет фона

val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
Этот вариант более производительный, чем использование Modifier.background()
. Modifier.background()
приемлем для однократной настройки цвета, но при анимации цвета в течение длительного времени это может привести к большему количеству перекомпозиций, чем необходимо.
Информацию о бесконечной анимации цвета фона см. в разделе «Повторение анимации» .
Анимировать размер компонуемого объекта

Compose позволяет анимировать размер компонуемых элементов несколькими способами. Используйте animateContentSize()
для анимации изменения размера компонуемых элементов.
Например, если у вас есть блок с текстом, который может расширяться от одной до нескольких строк, вы можете использовать Modifier.animateContentSize()
для достижения более плавного перехода:
var expanded by remember { mutableStateOf(false) } Box( modifier = Modifier .background(colorBlue) .animateContentSize() .height(if (expanded) 400.dp else 200.dp) .fillMaxWidth() .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { expanded = !expanded } ) { }
Вы также можете использовать AnimatedContent
с SizeTransform
для описания того, как должны происходить изменения размера.
Анимировать позицию компонуемого объекта

Чтобы анимировать положение составного объекта, используйте Modifier.offset{ }
в сочетании с animateIntOffsetAsState()
.
var moved by remember { mutableStateOf(false) } val pxToMove = with(LocalDensity.current) { 100.dp.toPx().roundToInt() } val offset by animateIntOffsetAsState( targetValue = if (moved) { IntOffset(pxToMove, pxToMove) } else { IntOffset.Zero }, label = "offset" ) Box( modifier = Modifier .offset { offset } .background(colorBlue) .size(100.dp) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { moved = !moved } )
Если вы хотите гарантировать, что компонуемые элементы не будут отображаться поверх или под другими компонуемыми элементами при анимации положения или размера, используйте Modifier.layout{ }
. Этот модификатор распространяет изменения размера и положения на родительский элемент, который затем влияет на другие дочерние элементы.
Например, если вы перемещаете Box
внутри Column
и другие дочерние элементы должны перемещаться при перемещении Box
, включите информацию о смещении с помощью Modifier.layout{ }
следующим образом:
var toggled by remember { mutableStateOf(false) } val interactionSource = remember { MutableInteractionSource() } Column( modifier = Modifier .padding(16.dp) .fillMaxSize() .clickable(indication = null, interactionSource = interactionSource) { toggled = !toggled } ) { val offsetTarget = if (toggled) { IntOffset(150, 150) } else { IntOffset.Zero } val offset = animateIntOffsetAsState( targetValue = offsetTarget, label = "offset" ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) Box( modifier = Modifier .layout { measurable, constraints -> val offsetValue = if (isLookingAhead) offsetTarget else offset.value val placeable = measurable.measure(constraints) layout(placeable.width + offsetValue.x, placeable.height + offsetValue.y) { placeable.placeRelative(offsetValue) } } .size(100.dp) .background(colorGreen) ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) }

Modifier.layout{ }
Анимированное заполнение компонуемого объекта

Чтобы анимировать заполнение компонуемого объекта, используйте animateDpAsState
в сочетании с Modifier.padding()
:
var toggled by remember { mutableStateOf(false) } val animatedPadding by animateDpAsState( if (toggled) { 0.dp } else { 20.dp }, label = "padding" ) Box( modifier = Modifier .aspectRatio(1f) .fillMaxSize() .padding(animatedPadding) .background(Color(0xff53D9A1)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { toggled = !toggled } )
Анимированное возвышение составного объекта
Для анимации высоты составного объекта используйте animateDpAsState
в сочетании с Modifier.graphicsLayer{ }
. Для однократного изменения высоты используйте Modifier.shadow()
. Для анимации тени более производительным вариантом будет использование модификатора Modifier.graphicsLayer{ }
.
val mutableInteractionSource = remember { MutableInteractionSource() } val pressed = mutableInteractionSource.collectIsPressedAsState() val elevation = animateDpAsState( targetValue = if (pressed.value) { 32.dp } else { 8.dp }, label = "elevation" ) Box( modifier = Modifier .size(100.dp) .align(Alignment.Center) .graphicsLayer { this.shadowElevation = elevation.value.toPx() } .clickable(interactionSource = mutableInteractionSource, indication = null) { } .background(colorGreen) ) { }
В качестве альтернативы можно использовать составную Card
и задать для свойства высоты различные значения для каждого штата.
Анимация масштабирования, перемещения или поворота текста

При анимации масштабирования, перемещения или поворота текста установите для параметра textMotion
в TextStyle
значение TextMotion.Animated
. Это обеспечит более плавные переходы между анимациями текста. Для перемещения, поворота или масштабирования текста используйте Modifier.graphicsLayer{ }
.
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val scale by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 8f, animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "scale" ) Box(modifier = Modifier.fillMaxSize()) { Text( text = "Hello", modifier = Modifier .graphicsLayer { scaleX = scale scaleY = scale transformOrigin = TransformOrigin.Center } .align(Alignment.Center), // Text composable does not take TextMotion as a parameter. // Provide it via style argument but make sure that we are copying from current theme style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated) ) }
Анимированный цвет текста

Чтобы анимировать цвет текста, используйте лямбда-функцию color
в компонуемом элементе BasicText
:
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val animatedColor by infiniteTransition.animateColor( initialValue = Color(0xFF60DDAD), targetValue = Color(0xFF4285F4), animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "color" ) BasicText( text = "Hello Compose", color = { animatedColor }, // ... )
Переключение между различными типами контента

Используйте AnimatedContent
для анимации между различными составными элементами. Если вам нужен просто стандартный эффект плавного перехода между составными элементами, используйте Crossfade
.
var state by remember { mutableStateOf(UiState.Loading) } AnimatedContent( state, transitionSpec = { fadeIn( animationSpec = tween(3000) ) togetherWith fadeOut(animationSpec = tween(3000)) }, modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { state = when (state) { UiState.Loading -> UiState.Loaded UiState.Loaded -> UiState.Error UiState.Error -> UiState.Loading } }, label = "Animated Content" ) { targetState -> when (targetState) { UiState.Loading -> { LoadingScreen() } UiState.Loaded -> { LoadedScreen() } UiState.Error -> { ErrorScreen() } } }
AnimatedContent
можно настроить для отображения различных типов переходов входа и выхода. Подробнее см. в документации по AnimatedContent
или в этой записи в блоге AnimatedContent
.
Анимация при навигации к разным пунктам назначения

Чтобы анимировать переходы между компонуемыми объектами при использовании артефакта navigation-compose , укажите для компонуемого объекта enterTransition
и exitTransition
. Вы также можете задать анимацию по умолчанию для всех пунктов назначения на верхнем уровне NavHost
:
val navController = rememberNavController() NavHost( navController = navController, startDestination = "landing", enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { composable("landing") { ScreenLanding( // ... ) } composable( "detail/{photoUrl}", arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }), enterTransition = { fadeIn( animationSpec = tween( 300, easing = LinearEasing ) ) + slideIntoContainer( animationSpec = tween(300, easing = EaseIn), towards = AnimatedContentTransitionScope.SlideDirection.Start ) }, exitTransition = { fadeOut( animationSpec = tween( 300, easing = LinearEasing ) ) + slideOutOfContainer( animationSpec = tween(300, easing = EaseOut), towards = AnimatedContentTransitionScope.SlideDirection.End ) } ) { backStackEntry -> ScreenDetails( // ... ) } }
Существует множество различных видов переходов входа и выхода, которые применяют различные эффекты к входящему и исходящему контенту. Подробнее см. в документации .
Повторить анимацию

Используйте rememberInfiniteTransition
с infiniteRepeatable
animationSpec
для непрерывного повторения анимации. Измените RepeatModes
, чтобы указать, как она должна повторяться.
Используйте finiteRepeatable
для повторения заданного количества раз.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Green, targetValue = Color.Blue, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(color) } ) { // your composable here }
Запустить анимацию при запуске компонуемого объекта
LaunchedEffect
срабатывает, когда компонуемый элемент попадает в композицию. Он запускает анимацию при запуске компонуемого элемента. Это можно использовать для управления изменением состояния анимации. Используйте Animatable
с методом animateTo
для запуска анимации при запуске:
val alphaAnimation = remember { Animatable(0f) } LaunchedEffect(Unit) { alphaAnimation.animateTo(1f) } Box( modifier = Modifier.graphicsLayer { alpha = alphaAnimation.value } )
Создание последовательных анимаций

Используйте API сопрограмм Animatable
для последовательной или параллельной анимации. Последовательный вызов animateTo
для Animatable
приводит к тому, что каждая анимация ожидает завершения предыдущей, прежде чем продолжить выполнение. Это связано с тем, что это функция приостановки.
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { alphaAnimation.animateTo(1f) yAnimation.animateTo(100f) yAnimation.animateTo(500f, animationSpec = tween(100)) }
Создавать параллельные анимации

Используйте API сопрограмм ( Animatable#animateTo()
или animate
) или API Transition
для реализации параллельных анимаций. При использовании нескольких функций запуска в контексте сопрограммы анимации запускаются одновременно:
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
Вы можете использовать API updateTransition
, чтобы использовать одно и то же состояние для одновременного управления множеством различных анимаций свойств. В примере ниже анимируются два свойства, управляемые изменением состояния: rect
и borderWidth
:
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "transition") val rect by transition.animateRect(label = "rect") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "borderWidth") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
Оптимизация производительности анимации
Анимация в Compose может вызывать проблемы с производительностью. Это связано с самой природой анимации: она заключается в быстром покадровом перемещении или изменении пикселей на экране для создания иллюзии движения.
Рассмотрим различные фазы Compose : композицию, макет и отрисовку. Если ваша анимация изменяет фазу макета, это требует перерисовки и перерисовки всех затронутых компоновочных элементов. Если анимация выполняется на фазе отрисовки, она по умолчанию более производительна, чем при запуске анимации на фазе макета, поскольку в этом случае требуется меньше работы.
Чтобы ваше приложение выполняло как можно меньше действий во время анимации, по возможности выбирайте лямбда-версию Modifier
. Это позволяет пропустить перекомпозицию и выполнить анимацию вне фазы композиции. В противном случае используйте Modifier.graphicsLayer{ }
, так как этот модификатор всегда выполняется на фазе отрисовки. Подробнее об этом см. в разделе «Отложенное чтение» документации по производительности.
Изменить время анимации
В Compose по умолчанию для большинства анимаций используются пружинные анимации. Пружинные анимации, или анимации, основанные на законах физики, выглядят более естественно. Кроме того, их можно прерывать, поскольку они учитывают текущую скорость объекта, а не фиксированное время. Если вы хотите переопределить настройки по умолчанию, все API анимации, показанные выше, позволяют задать animationSpec
для настройки анимации: хотите ли вы, чтобы она выполнялась в течение определённой продолжительности или была более упругой.
Ниже приведен обзор различных параметров animationSpec
:
-
spring
: анимация, основанная на законах физики, используется по умолчанию для всех анимаций. Вы можете изменить жесткость или коэффициент затухания, чтобы добиться другого внешнего вида и ощущения от анимации. -
tween
(сокращение от between ): анимация на основе длительности, анимация между двумя значениями с функциейEasing
. -
keyframes
: спецификация для указания значений в определенных ключевых точках анимации. -
repeatable
: спецификация на основе длительности, которая выполняется определенное количество раз, указанноеRepeatMode
. -
infiniteRepeatable
: спецификация, основанная на длительности, которая выполняется вечно. -
snap
: мгновенная привязка к конечному значению без какой-либо анимации.

Более подробную информацию об animationSpecs можно найти в полной документации.
Дополнительные ресурсы
Больше примеров забавной анимации в Compose можно найти здесь:
- 5 быстрых анимаций в Compose
- Заставляем медуз двигаться в Compose
- Настройка
AnimatedContent
в Compose - Функции Easing в Compose