Функция keyframes и анимация по ключевым кадрам

Последнее обновление: 13.04.2024

Ключевые кадры (keyframes) позволяют применять различные значения длительности и замедления в определенных точках временной шкалы анимации. Ключевые кадры применяются к анимации через параметр animationSpec и определяются с помощью функции keyframes():

public fun <T> keyframes(
    init: KeyframesSpec.KeyframesSpecConfig<T>.() -> Unit
): KeyframesSpec<T>

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

Определение анимации по ключевым кадрам содержит свойства durationMillis (общее время анимации) и delayMillis (задержка анимации - необязательное свойство), а также определения ключевых кадров. Каждый ключевой кадр содержит метку времени, которая указывает, какая часть общей анимации должна быть завершена в этот момент в зависимости от типа единицы состояния (например, Float, Dp, Int и т. д.). Эти временные метки создаются посредством вызовов функции at(). Например:

animationSpec = keyframes {

        durationMillis = 1000
        100.dp.at(10)
        110.dp.at(500)
        200.dp.at(700)
    }

Здесь общее время анимации (durationMillis) 1000 миллисекунд. Для этой анимации задается три ключевых кадра. К примеру, первый кадр 100.dp.at(10) указывает, что смещение в 100.dp надо достигнуть через 10 миллисекунд. При 500 миллисекундах смещение должно составлять 110dp и, наконец, 200dp по истечении 700 миллисекунд. Это оставляет 300 миллисекунд для завершения оставшейся анимации.

Рассмотрим применение анимации по ключевым кадрам на следующем примере:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.keyframes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val boxHeight = 150         // высота Box
            val startOffset = 10        // начальный отступ
            val endOffset = LocalConfiguration.current.screenHeightDp - boxHeight   // конечный отступ
            var boxState by remember { mutableStateOf(startOffset)}

            val offset: Dp by animateDpAsState(
                targetValue = boxState.dp,
                animationSpec = keyframes {
                    durationMillis = 1000
                    if (boxState==endOffset) {
                        100.dp.at(100)
                        110.dp.at(500)
                        200.dp.at(800)
                    }
                }
            )

            Row(Modifier.fillMaxSize()) {
                println("Offset: ${offset.value}")
                Button({boxState = if (boxState==startOffset) endOffset else startOffset },
                    Modifier.padding(10.dp)) {
                    Text("Start", fontSize = 22.sp)
                }
                Box(Modifier.padding(top=offset).size(boxHeight.dp).background(Color.DarkGray))
            }
        }
    }
}

Здесь мы анимируем движение компонента Box по вертикали сверху вниз и обратно:

Анимация по ключевым кадрам и функция keyframes в Jetpack Compose на Kotlin в Android

Для целей анимации определяем ряд переменных, как начальная и конечная позиция круга и его диаметр (высота)

val boxHeight = 150         // высота Box
val startOffset = 10        // начальный отступ
val endOffset = LocalConfiguration.current.screenHeightDp - boxHeight   // конечный отступ

Далее определем состояние, которое будет хранить текущий текущий отступ с верху:

var boxState by remember { mutableStateOf(endOffset)}

Затем создаем анимацию с помощью функции animateDpAsState(), которая будет определять отступ:

val offset: Dp by animateDpAsState(
    targetValue = boxState.dp,
    animationSpec = 
        keyframes {
            durationMillis = 1000
            if (boxState==endOffset) {
                100.dp.at(100)
                110.dp.at(500)
                200.dp.at(800)
            }
        }
)

Здесь прежде всего определяем targetValue - значение, к которому надо перейти в процессе анимации. Поскольку при нажати на кнопку изменяется boxState, то именно эту переменная и будет хранить следующее состояние, к которому надо выполнить анимационный переход. Соответственно просто передаем параметру targetValue значение этой переменной, преобразуя его в объект Dp.

Параметру animationSpec передаем результат функции keyframes(). В ней устанавливаем общее время анимации - 1000 миллисекунд. И если надо перейти к конечному отступу (в boxState хранится endOffset), то есть box находится вверху, то задаем три ключевых кадра. Если Box находится внизу, то ключевые кадры отсутствуют, а Box будет двигаться на начальную позицию равномерно.

Для запуска анимации определяем кнопку, которая меняет значение boxState и тем самым вызывает анимацию:

Button({boxState = if (boxState==startOffset) endOffset else startOffset },

И в конце идет собственно компонент Box, у которого анимируется отступ сверху:

Box(Modifier.padding(top=offset).size(boxHeight.dp).background(Color.DarkGray))

Его значение отступа с верху привязано к переменной offset, которая генерируется функцией animateDpAsState()

Стоит отметить, что в качестве альтернативы для установки ключевых кадров в функции keyframes() мы могли бы использовать другой синтаксис, где функция at() применялась бы аналогично операторам:

animationSpec = keyframes {

    durationMillis = 1000
    if (boxState==endOffset) {
        100.dp at 100
        110.dp at 500
        200.dp at 800
    }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850