Я работаю со списком из трех элементов, скажем [A, B, C]
, и пытаюсь создать с их помощью анимацию вращения. Последовательность этой анимации будет такой: AB, BC, CA, AB и т. д., повторяясь бесконечно. В списке также может быть n объектов, и я хочу, чтобы он вращался бесконечно.
Однако я столкнулся с проблемой обеспечения плавного перехода между элементами. Кажется, наблюдается заметный эффект «мигания», нарушающий плавность анимации. Мне нужен совет, как устранить эту проблему, или предложения по альтернативным методам достижения того же эффекта.
Желаемый эффект, к которому я стремлюсь, показан ниже. Но при наблюдении вы можете заметить, что анимация не является непрерывной и возникает определенный эффект «мигания».
@Composable
fun FollowEventCardHome() {
val data = "{...}"
val eventData = Gson().fromJson(data, EventData::class.java)
var currentEvent by remember { mutableStateOf(eventData.events?.getOrNull(0)) }
var nextEvent by remember { mutableStateOf(eventData.events?.getOrNull(1)) }
var currentIndex by remember { mutableIntStateOf(0) }
var isAnimate by remember { mutableStateOf(true) }
val configuration = LocalConfiguration.current
val cardWidth = configuration.screenWidthDp.dp - 55.dp
if (isAnimate) {
StartEndInfo(
nextEvent = nextEvent,
currentEvent = currentEvent,
isAnimate = true,
isAnimationFinished = {
isAnimate = false
if (eventData.events?.size!! > 1) {
if (currentIndex == eventData.events?.size!! - 1) {
currentIndex = 0
} else {
currentIndex++
}
}
currentEvent = eventData.events?.getOrNull(currentIndex)
nextEvent = if (currentIndex < eventData.events?.size!!) {
eventData.events?.getOrNull(0)
} else {
eventData.events?.getOrNull(currentIndex + 1)
}
}
)
}
if (!isAnimate){
StartEndInfo(
nextEvent = nextEvent,
currentEvent = currentEvent,
isAnimate = true,
isAnimationFinished = {
isAnimate = true
if (eventData.events?.size!! > 1) {
if (currentIndex == eventData.events?.size!! - 1) {
currentIndex = 0
} else {
currentIndex++
}
}
currentEvent = eventData.events?.getOrNull(currentIndex)
nextEvent = if (currentIndex < eventData.events?.size!!) {
eventData.events?.getOrNull(0)
} else {
eventData.events?.getOrNull(currentIndex + 1)
}
}
)
}
}
@Composable
fun StartEndInfo(
nextEvent: Event? = null,
currentEvent: Event? = null,
isAnimate: Boolean,
isAnimationFinished: () -> Unit
) {
Row(
modifier = Modifier
.padding(
start = 16.dp,
bottom = 16.dp
)
.fillMaxWidth()
) {
Column(modifier = Modifier.fillMaxSize()) {
StartEndWithProgress(
nextEvent = nextEvent,
currentEvent = currentEvent,
isAnimate = isAnimate,
isAnimationFinished = isAnimationFinished
)
}
}
}
@Composable
fun StartEndWithProgress(
nextEvent: Event?,
currentEvent: Event?,
isAnimate: Boolean,
isAnimationFinished: () -> Unit
) {
val scalePreviousEvent = remember { Animatable(1f) }
val scaleCurrentEvent = remember { Animatable(0f ) }
LaunchedEffect(isAnimate) {
scalePreviousEvent.animateTo(
targetValue = 0f,
animationSpec = tween(durationMillis = 4000)
)
}
LaunchedEffect(key1 = isAnimate) {
scaleCurrentEvent.animateTo(
targetValue = 1f,
animationSpec = tween(durationMillis = 4000)
)
}
if (isAnimate && scalePreviousEvent.value == 0f || !isAnimate && scalePreviousEvent.value == 1f){
isAnimationFinished()
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 30.dp)
.padding(top = 150.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Box {
currentEvent?.start?.locationCode.let {
Text(
text = currentEvent?.start?.locationCode ?: "",
fontSize = 20.sp,
modifier = Modifier.graphicsLayer {
scaleX = 1f
scaleY = if (isAnimate) {
scaleCurrentEvent.value
} else 1f
transformOrigin = TransformOrigin(0.5f, 1f)
}
)
Text(
text = nextEvent?.start?.locationCode ?: "",
fontSize = 20.sp,
modifier = Modifier.graphicsLayer {
scaleX = 1f
scaleY = if (isAnimate) {
scalePreviousEvent.value
} else 0f
transformOrigin = TransformOrigin(0.5f, 0f)
}
)
}
}
Box {
currentEvent?.end?.locationCode.let {
Text(
text = currentEvent?.end?.locationCode ?: "",
fontSize = 20.sp,
modifier = Modifier.graphicsLayer {
scaleX = 1f
scaleY = if (isAnimate) { scaleCurrentEvent.value } else 1f
transformOrigin = TransformOrigin(0.5f, 1f)
}
)
}
Text(
text = nextEvent?.end?.locationCode ?: "",
fontSize = 20.sp,
modifier = Modifier.graphicsLayer {
scaleX = 1f
scaleY = if (isAnimate) {
scalePreviousEvent.value
} else 0f
transformOrigin = TransformOrigin(0.5f, 0f)
}
)
}
}
}
Не меняя вашего текущего подхода к использованию Animatable
с graphicsLayer
, я просто выделил соответствующие части в специальный компонуемый объект и немного его упростил:
@Composable
fun <T> AnimateRotatingList(
list: List<T>,
modifier: Modifier = Modifier,
animationSpec: AnimationSpec<Float> = tween(durationMillis = 4000),
itemContent: @Composable (item: T) -> Unit,
) {
Box(modifier = modifier) {
if (list.isEmpty()) return
if (list.size == 1) {
itemContent(list.first())
return
}
var currentIndex by remember(list) { mutableIntStateOf(0) }
val current = remember(list, currentIndex) { list[currentIndex] }
val next = remember(list, currentIndex) { list[(currentIndex + 1) % list.size] }
val scale = remember(list) { Animatable(1f) }
LaunchedEffect(scale, animationSpec) {
while (true) {
scale.animateTo(
targetValue = 0f,
animationSpec = animationSpec,
)
scale.snapTo(1f)
currentIndex = (currentIndex + 1) % list.size
}
}
Box(modifier = Modifier.graphicsLayer {
scaleY = scale.value
transformOrigin = TransformOrigin(0.5f, 0f)
}) {
itemContent(current)
}
Box(modifier = Modifier.graphicsLayer {
scaleY = 1 - scale.value
transformOrigin = TransformOrigin(0.5f, 1f)
}) {
itemContent(next)
}
}
}
Я не знаю, где именно была ваша ошибка, но эта подчищенная версия теперь работает как положено:
@Composable
fun FollowEventCardHome() {
val eventData: EventData = TODO()
AnimateRotatingList(
list = eventData.events.orEmpty(),
modifier = Modifier
.padding(top = 150.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 30.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = it.start.locationCode,
fontSize = 20.sp,
)
Text(
text = it.end.locationCode,
fontSize = 20.sp,
)
}
}
}
Пожалуйста, отредактируйте свой вопрос и сократите код до минимально воспроизводимого примера. Там много лишнего кода, который не компилируется из-за отсутствия зависимостей.