Большинство функций, которые устанавливают анимацию, имеют параметр animationSpec
, который представляет интерфейс AnimationSpec.
Данному параметру передается объект одного из классов, которые реализуют AnimationSpec. В частности, с помощью этого параметра можно настроить
продолжительность анимации, задержку, эффект отскок, повторение и замедление анимации и ряд других моментов.
Например, рассмотренная в прощлой теме функция animateDpAsState(), которая выполняет анимацию значений Dp, также имеет подобный параметр:
@Composable public fun animateDpAsState( targetValue: Dp, animationSpec: AnimationSpec<Dp> = dpDefaultSpring, // установка анимации label: String = "DpAnimation", finishedListener: ((Dp) -> Unit)? = null ): State<Dp>
Для создания объекта AnimationSpec
Jetpack Compose предоставляет множество специальных функций и значений. Рассмотрим наиболее используемую из них - функцию tween().
Она имеет следующую сигнатуру:
public fun <T> tween( durationMillis: Int = DefaultDurationMillis, delayMillis: Int = 0, easing: Easing = FastOutSlowInEasing ): TweenSpec<T>
Она принимает следующие параметры:
durationMillis
: длительность анимации в миллисекундах
delayMillis
: устанавливает начальную задержку
easing
: позволяет ускорять и замедлять анимацию
Наиболее распространенный сценарий использования функции tween() - настройка времени анимации. Например:
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.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth 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.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.platform.LocalConfiguration class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val boxWidth = 150 // ширина компонента val startOffset = 0 // начальная позиция val endOffset = LocalConfiguration.current.screenWidthDp - boxWidth // конечная позиция var boxState by remember { mutableStateOf(startOffset)} val offset by animateDpAsState( targetValue = boxState.dp, animationSpec = tween(durationMillis = 5000) // анимация длится 5 секунд ) Column(Modifier.fillMaxWidth()) { Box(Modifier.padding(start=offset).size(boxWidth.dp).background(Color.DarkGray)) Button({boxState = if (boxState==startOffset) {endOffset} else {startOffset} }, Modifier.padding(10.dp)) { Text("Move", fontSize = 25.sp) } } } } }
В данном случае по нажатию на кнопку происходит перемещение компонента с помощью анимации значений Dp:
Здесь в начале определяем некоторые значения, которые будут вычисляться для изменения позиции компонента Box:
val boxWidth = 150 // ширина компонента val startOffset = 0 // начальная позиция val endOffset = LocalConfiguration.current.screenWidthDp - boxWidth // конечная позиция
То есть наш Box имеет ширину boxWidth и будет перемещаться от позиции startOffset до endOffset.
Далее определяем состояние, от которого будет зависеть позиция компонента Box:
var boxState by remember { mutableStateOf(startOffset)}
Затем определяем анимацию позиции Box с помощью функции animateDpAsState()
val offset by animateDpAsState( targetValue = boxState.dp, animationSpec = tween(durationMillis = 5000) // анимация длится 5 секунд )
Параметр targetValue
на основании boxState определяет позицию, к которой надо выполнить переход. А параметру animationSpec
присваивается значение функции tween()
,
в которую передается число 5000. То есть анимация будет длиться 5 миллисекунд.
Результат функцит animateDpAsState()
передаем в переменну offset и далее используем ее для установки отступа от начала контейнера для компонента Box:
Box(Modifier.padding(start=offset) // устанавливаем отступ .size(boxWidth.dp) .background(Color.DarkGray))
И для запуска анимации на кнопку Move вешаем обработчик нажатия, который переключает значение boxState со startOffset на endOffset и обратно.
Button({boxState = if (boxState==startOffset) {endOffset} else {startOffset} },
Параметр easing в функции tween()
позволяет ускорять и замедлять анимацию. В качестве значения можно передать одно из значений, определенных в пакете androidx.compose.animation.core.
:
FastOutSlowInEasing
LinearOutSlowInEasing
FastOutLinearEasing
LinearEasing
CubicBezierEasing
Рассмотрим их применение:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth 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.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.platform.LocalConfiguration class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val boxWidth = 150 val startOffset = 0 val endOffset = LocalConfiguration.current.screenWidthDp - boxWidth var boxState by remember { mutableStateOf(startOffset)} val offset by animateDpAsState( targetValue = boxState.dp, animationSpec = tween(durationMillis = 5000, easing = FastOutSlowInEasing) // анимация длится 5 секунд ) val offset1 by animateDpAsState( targetValue = boxState.dp, animationSpec = tween(durationMillis = 5000, easing = LinearOutSlowInEasing) // анимация длится 5 секунд ) Column(Modifier.fillMaxWidth()) { Box(Modifier.padding(start=offset).size(boxWidth.dp).background(Color.DarkGray)) Box(Modifier.padding(start=offset1, top=10.dp).size(boxWidth.dp).background(Color.DarkGray)) Button({boxState = if (boxState==startOffset) {endOffset} else {startOffset} }, Modifier.padding(10.dp)) { Text("Move", fontSize = 25.sp) } } } } }
В данном случае я добавил второй компонент Box для сравнения. Для каждого из них применяется анимация в течение 5 секунд, однако значение параметров easing отличается:
val offset by animateDpAsState( targetValue = boxState.dp, animationSpec = tween(durationMillis = 5000, easing = FastOutSlowInEasing) // анимация длится 5 секунд ) val offset1 by animateDpAsState( targetValue = boxState.dp, animationSpec = tween(durationMillis = 5000, easing = LinearOutSlowInEasing) // анимация длится 5 секунд )
Что повлияет на принцип изменение отступа:
Если для параметра easing
применяется значение CubicBezierEasing, то в конструктор надо передать координаты двух контрольных точек кривой Безье, на основании
которой расчитывается анимация:
import androidx.compose.animation.core.CubicBezierEasing ....................... val offset by animateDpAsState( targetValue = boxState.dp, animationSpec = tween(durationMillis = 5000, easing = CubicBezierEasing(0f, 1f, 0.5f,1f)) )