Краткое руководство по анимации в Compose

Compose имеет множество встроенных механизмов анимации, и может быть сложно понять, какой из них выбрать. Ниже приведен список распространенных вариантов использования анимации. Для получения более подробной информации о полном наборе различных доступных вам опций API прочтите полную документацию Compose Animation .

Анимация общих компонуемых свойств

Compose предоставляет удобные API, которые позволяют решать многие распространенные случаи использования анимации. В этом разделе показано, как можно анимировать распространенные свойства компонуемого объекта.

Анимация появления/исчезновения

Зеленый составной объект, показывающий и скрывающий себя
Рисунок 1. Анимация появления и исчезновения элемента в столбце

Используйте 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 в конечном итоге удаляет элемент из композиции.

Анимация альфа-канала составного объекта
Рисунок 2. Анимация альфа-канала составного объекта

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

Можно создавать композиции с фоновым цветом, который со временем меняется в виде анимации, где цвета плавно переходят друг в друга.
Рисунок 3. Анимация фонового цвета компонуемого объекта

val animatedColor by animateColorAsState(
    if (animateBackgroundColor) colorGreen else colorBlue,
    label = "color"
)
Column(
    modifier = Modifier.drawBehind {
        drawRect(animatedColor)
    }
) {
    // your composable here
}

Этот вариант более производительный, чем использование Modifier.background() . Modifier.background() приемлем для одноразовой настройки цвета, но при анимации цвета с течением времени это может привести к большему количеству перекомпозиций, чем необходимо.

Для бесконечной анимации цвета фона см. раздел «Повторение анимации» .

Анимировать размер компонуемого объекта

Зеленый компонуемый, анимированный, плавно меняющий размер.
Рисунок 4. Плавная анимация композиции между маленьким и большим размером

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 для описания того, как должны происходить изменения размера.

Анимировать положение компонуемого объекта

Зеленый, плавно анимируемый, перемещается вниз и вправо
Рисунок 5. Композитное перемещение со смещением

Чтобы анимировать положение составного объекта, используйте 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)
    )
}

2 блока, второй блок анимирует свое положение по осям X и Y, третий блок реагирует, также перемещаясь на величину Y.
Рисунок 6. Анимация с помощью Modifier.layout{ }

Анимация заполнения компонуемого объекта

Зеленый компонуемый элемент становится меньше и больше по щелчку, отступы анимированы
Рисунок 7. Компоновка с анимированным отступом

Чтобы анимировать заполнение компонуемого объекта, используйте 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
        }
)

Анимированное возвышение составного объекта

Рисунок 8. Анимация возвышения 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(colorGreen)
) {
}

В качестве альтернативы можно использовать составной элемент Card и задать для свойства высоты различные значения для каждого штата.

Анимация масштабирования, перемещения или поворота текста

Текстовое сочинение высказывание
Рисунок 9. Текст плавно анимируется между двумя размерами

При анимации масштабирования, перемещения или поворота текста установите параметр 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)
    )
}

Анимировать цвет текста

Слова
Рисунок 10. Пример, показывающий анимированный цвет текста

Чтобы анимировать цвет текста, используйте лямбда-функцию 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
    },
    // ...
)

Переключение между различными типами контента

Зеленый экран говорит
Рисунок 11. Использование AnimatedContent для анимации изменений между различными компонуемыми объектами (замедленно)

Используйте 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 .

Анимация при навигации к разным пунктам назначения

Два составных элемента, один зеленый с надписью «Landing» и один синий с надписью «Detail», анимируются путем наложения составного элемента «detail» на составной элемент «landing».
Рисунок 12. Анимация между компонуемыми элементами с помощью navigation-compose

Для анимации переходов между компонуемыми при использовании артефакта 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(
            // ...
        )
    }
}

Существует множество различных видов входных и выходных переходов, которые применяют различные эффекты к входящему и исходящему контенту. Более подробную информацию см. в документации .

Повторить анимацию

Зеленый фон, который бесконечно трансформируется в синий фон путем анимации между двумя цветами.
Рисунок 13. Цвет фона, анимируемый между двумя значениями, бесконечно

Используйте 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 запускается, когда composable входит в композицию. Он запускает анимацию при запуске composable, вы можете использовать это для управления изменением состояния анимации. Использование Animatable с методом animateTo для запуска анимации при запуске:

val alphaAnimation = remember {
    Animatable(0f)
}
LaunchedEffect(Unit) {
    alphaAnimation.animateTo(1f)
}
Box(
    modifier = Modifier.graphicsLayer {
        alpha = alphaAnimation.value
    }
)

Создание последовательных анимаций

Четыре круга с зелеными стрелками, анимированными между ними, один за другим.
Рисунок 14. Диаграмма, показывающая, как последовательно развивается анимация, один за другим.

Используйте 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))
}

Создавайте параллельные анимации

Три круга с зелеными стрелками, движущимися к каждому из них, и все они одновременно движущиеся.
Рисунок 15. Диаграмма, показывающая, как развиваются параллельные анимации, все одновременно.

Используйте 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 : мгновенно привязывается к конечному значению без какой-либо анимации.
Напишите здесь свой альтернативный текст
Рисунок 16. Отсутствие набора спецификаций против набора спецификаций Custom Spring

Дополнительную информацию о animationSpecs можно найти в полной документации.

Дополнительные ресурсы

Больше примеров забавной анимации в Compose можно найти здесь:

,

Compose имеет множество встроенных механизмов анимации, и может быть сложно понять, какой из них выбрать. Ниже приведен список распространенных вариантов использования анимации. Для получения более подробной информации о полном наборе различных доступных вам опций API прочтите полную документацию Compose Animation .

Анимация общих компонуемых свойств

Compose предоставляет удобные API, которые позволяют решать многие распространенные случаи использования анимации. В этом разделе показано, как можно анимировать распространенные свойства компонуемого объекта.

Анимация появления/исчезновения

Зеленый составной объект, показывающий и скрывающий себя
Рисунок 1. Анимация появления и исчезновения элемента в столбце

Используйте 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 в конечном итоге удаляет элемент из композиции.

Анимация альфа-канала составного объекта
Рисунок 2. Анимация альфа-канала составного объекта

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

Можно создавать композиции с фоновым цветом, который со временем меняется в виде анимации, где цвета плавно переходят друг в друга.
Рисунок 3. Анимация фонового цвета компонуемого объекта

val animatedColor by animateColorAsState(
    if (animateBackgroundColor) colorGreen else colorBlue,
    label = "color"
)
Column(
    modifier = Modifier.drawBehind {
        drawRect(animatedColor)
    }
) {
    // your composable here
}

Этот вариант более производительный, чем использование Modifier.background() . Modifier.background() приемлем для одноразовой настройки цвета, но при анимации цвета с течением времени это может привести к большему количеству перекомпозиций, чем необходимо.

Для бесконечной анимации цвета фона см. раздел «Повторение анимации» .

Анимировать размер компонуемого объекта

Зеленый компонуемый, анимированный, плавно меняющий размер.
Рисунок 4. Плавная анимация композиции между маленьким и большим размером

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 для описания того, как должны происходить изменения размера.

Анимировать положение компонуемого объекта

Зеленый, плавно анимируемый, перемещается вниз и вправо
Рисунок 5. Композитное перемещение со смещением

Чтобы анимировать положение составного объекта, используйте 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)
    )
}

2 блока, второй блок анимирует свое положение по осям X и Y, третий блок реагирует, также перемещаясь на величину Y.
Рисунок 6. Анимация с помощью Modifier.layout{ }

Анимация заполнения компонуемого объекта

Зеленый компонуемый элемент становится меньше и больше по щелчку, отступы анимированы
Рисунок 7. Компоновка с анимированным отступом

Чтобы анимировать заполнение компонуемого объекта, используйте 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
        }
)

Анимированное возвышение составного объекта

Рисунок 8. Анимация возвышения 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(colorGreen)
) {
}

В качестве альтернативы можно использовать составной элемент Card и задать для свойства высоты различные значения для каждого штата.

Анимация масштабирования, перемещения или поворота текста

Текстовое сочинение высказывание
Рисунок 9. Текст плавно анимируется между двумя размерами

При анимации масштабирования, перемещения или поворота текста установите параметр 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)
    )
}

Анимировать цвет текста

Слова
Рисунок 10. Пример, показывающий анимированный цвет текста

Чтобы анимировать цвет текста, используйте лямбда-функцию 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
    },
    // ...
)

Переключение между различными типами контента

Зеленый экран говорит
Рисунок 11. Использование AnimatedContent для анимации изменений между различными компонуемыми объектами (замедленно)

Используйте 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 .

Анимация при навигации к разным пунктам назначения

Два составных элемента, один зеленый с надписью «Landing» и один синий с надписью «Detail», анимируются путем наложения составного элемента «detail» на составной элемент «landing».
Рисунок 12. Анимация между компонуемыми элементами с помощью navigation-compose

Для анимации переходов между компонуемыми при использовании артефакта 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(
            // ...
        )
    }
}

Существует множество различных видов входных и выходных переходов, которые применяют различные эффекты к входящему и исходящему контенту. Более подробную информацию см. в документации .

Повторить анимацию

Зеленый фон, который бесконечно трансформируется в синий фон путем анимации между двумя цветами.
Рисунок 13. Цвет фона, анимируемый между двумя значениями, бесконечно

Используйте 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 запускается, когда composable входит в композицию. Он запускает анимацию при запуске composable, вы можете использовать это для управления изменением состояния анимации. Использование Animatable с методом animateTo для запуска анимации при запуске:

val alphaAnimation = remember {
    Animatable(0f)
}
LaunchedEffect(Unit) {
    alphaAnimation.animateTo(1f)
}
Box(
    modifier = Modifier.graphicsLayer {
        alpha = alphaAnimation.value
    }
)

Создание последовательных анимаций

Четыре круга с зелеными стрелками, анимированными между ними, один за другим.
Рисунок 14. Диаграмма, показывающая, как последовательно развивается анимация, один за другим.

Используйте 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))
}

Создавайте параллельные анимации

Три круга с зелеными стрелками, движущимися к каждому из них, и все они одновременно движущиеся.
Рисунок 15. Диаграмма, показывающая, как развиваются параллельные анимации, все одновременно.

Используйте 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 : мгновенно привязывается к конечному значению без какой-либо анимации.
Напишите здесь свой альтернативный текст
Рисунок 16. Отсутствие набора спецификаций против набора спецификаций Custom Spring

Дополнительную информацию о animationSpecs можно найти в полной документации.

Дополнительные ресурсы

Больше примеров забавной анимации в Compose можно найти здесь: