В Compose есть много встроенных механизмов анимации, и может быть сложно понять, какой из них выбрать. Ниже приведен список распространенных вариантов использования анимации. Подробную информацию обо всех доступных вариантах API можно найти в документации по Compose Animation.
Анимация общих свойств composable-функций
Compose предоставляет удобные API, которые позволяют решать многие распространенные задачи, связанные с анимацией. В этом разделе показано, как анимировать общие свойства composable-функции.
Анимация появления/исчезновения
Используйте 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 // ... }
Параметры enter и exit в AnimatedVisibility позволяют настроить поведение composable-функции при появлении и исчезновении. Подробную информацию вы найдете в полной документации.
Ещё один вариант анимации видимости composable-функции – анимировать альфа-канал с течением времени с помощью 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.TopCenckGuide.kt
Однако при изменении альфа-канала следует учитывать, что composable-функция остается в композиции и продолжает занимать пространство, в котором она расположена. Из-за этого программы чтения с экрана и другие специальные возможности могут по-прежнему считать, что элемент находится на экране. С другой стороны, AnimatedVisibility в конечном итоге удаляет элемент из композиции.
Анимировать цвет фона
val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composackGuide.kt
Этот вариант эффективнее, чем использование Modifier.background().
Modifier.background() подходит для однократной настройки цвета, но при анимации цвета во времени это может привести к большему количеству перекомпоновок, чем необходимо.
Чтобы зациклить анимацию цвета фона, ознакомьтесь с разделом Повтор анимации.
Как анимировать размер composable-функции
Compose позволяет анимировать размер составных элементов несколькими способами. Используйте animateContentSize() для анимации между изменениями размера composable-функции.
Например, если у вас есть поле с текстом, который может занимать от одной до нескольких строк, вы можете использовать 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, чтобы описать, как должен меняться размер.
Как анимировать положение composable-функции
Чтобы анимировать положение composable-функции, используйте 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 ckGuide.kt
Если вы хотите, чтобы composable-функции не накладывались друг на друга при анимации положения или размера, используйте 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, con>straints - 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(colorBluckGuide.kt
Modifier.layout{ }Как анимировать отступы composable-функции
Чтобы анимировать отступы composable-функции, используйте 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 ckGuide.kt
Как анимировать высоту composable-функции
Чтобы анимировать высоту composable-функции, используйте 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(colorGrckGuide.kt
Вы также можете использовать composable-функцию Card и задать разные значения свойства elevation для каждого состояния.
Анимация масштабирования, перемещения или вращения текста
При анимации масштаба, перемещения или поворота текста задайте для параметра 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 = ) } AnimationQuickGuide.kt
Анимировать цвет текста
Чтобы анимировать цвет текста, используйте лямбда-выражение color в composable-функции 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 = { ani// ... )AnimationQuickGuide.kt
Переключайтесь между разными типами контента
Используйте AnimatedContent для анимации между разными composable-функциями. Если вам нужно стандартное затухание между ними, используйте 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" ) { tar>getState - when (targetState) { UiState>.Loading - { LoadingScreen() } UiStat>e.Loaded - { LoadedScreen() } UiSta>te.Error - { ErrorScreen() ckGuide.kt
AnimatedContent можно настроить так, чтобы показывать разные виды переходов при входе и выходе. Дополнительную информацию вы найдете в документации по AnimatedContent и в записи блога о AnimatedContent.
Анимация при переходе к разным пунктам назначения
Чтобы анимировать переходы между composable-функциями при использовании артефакта navigation-compose, укажите enterTransition и exitTransition в composable-функции. Вы также можете задать анимацию по умолчанию для всех пунктов назначения на верхнем уровне 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 - ScreenDetai ) } }AnimationQuickGuide.kt
Существует множество различных типов переходов входа и выхода, которые применяют различные эффекты к входящему и исходящему контенту. Дополнительную информацию см. в документации.
Как повторить анимацию
Используйте rememberInfiniteTransition с infiniteRepeatable
animationSpec, чтобы анимация повторялась непрерывно. Измените RepeatMode, чтобы указать, как он должен двигаться туда и обратно.
Используйте repeatable, чтобы повторить действие заданное количество раз.
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) } ) { // yoimationQuickGuide.kt
Как запустить анимацию при запуске composable-функции
LaunchedEffect запускается, когда composable-функция входит в композицию. Она запускает анимацию при запуске composable-функции. Вы можете использовать ее для управления изменением состояния анимации. Использование 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 = tweckGuide.kt
Как создать параллельные анимации
Чтобы запустить параллельные анимации, используйте 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(100ckGuide.kt
Вы можете использовать API updateTransition, чтобы использовать одно и то же состояние для одновременного управления множеством различных анимаций свойств. В следующем примере анимируются два свойства, управляемые изменением состояния, – rect и borderWidth:
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "transition") val rect by transition.animateRect(label = "r>ect") { state - when (state) { >BoxState.Collapsed - Rect(0f, 0f, 100f, 100f) > BoxState.Expanded - Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = &quo>t;borderWidth") { state - when (state) >{ BoxState.Collapsed - 1.>dp Bo } }AnimationQuickGuide.kt
Оптимизация эффективности анимации
Анимации в 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 - Функции плавности в Compose