Для повторения анимации применяется класс RepeatableSpec (который реализует интерфейс AnimationSpec). Объект этого класса можно получить с помощью функции repeatable():
public fun <T> repeatable( iterations: Int, animation: DurationBasedAnimationSpec<T>, repeatMode: RepeatMode = RepeatMode.Restart, initialStartOffset: StartOffset = StartOffset(0) ): RepeatableSpec<T>
Она принимает следующие параметры:
iterations
: количество повторений
animation
: спецификация анимации. Можно использовать другие функции, которые возвращают DurationBasedAnimationSpec, например, функцию tween()
repeatMode
: значение RepeatMode, которое указывает, должна
ли анимация выполняться от начала до конца (значение RepeatMode.Restart) или должна выполняться в обратном порядке - от конца к началу (значение
RepeatMode.Reverse)
initialStartOffset
: смещение отностельно начала
В данном случае первые два параметра обязательные. Например, повторим анимацию 3 раза:
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.repeatable 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 = repeatable(3, animation = tween(2000)) ) 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) } } } } }
В данном случае по нажатию на кнопку запускается анимация, которая перемещает компонента Box от начала до конца по горизонтали, причем процесс повторяется три раза:
Здесь в начале определяем некоторые значения, которые будут вычисляться для изменения позиции компонента 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 = repeatable(3, animation = tween(2000)) )
Параметр targetValue
на основании boxState определяет позицию, к которой надо выполнить переход. А параметру animationSpec
присваивается значение функции
repeatable()
. Первый аргумент функции - число 3 указывает на количество повторений. А параметру animation
получает результат функции tween()
,
которая устанавливает время анимации - 2000 миллисекунд.
Результат функцит 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} },
Однако при выполнения анимации после ее завершения компонент Box резко перемещается на начальную позицию и с нее уже начинает вторую итерацию анимации. Параметр repeatMode
при значении RepeatMode.Reverse
позволяет задать воспроизведение анимации в обратном порядке:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.repeatable 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, // 3 раза повторяем анимацию в течение 2 секунд, повторяем в обратном порядке animationSpec = repeatable(3, animation = tween(2000), repeatMode = RepeatMode.Reverse) ) 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) } } } } }