我正在使用三个元素的列表,例如
[A, B, C]
,并且我正在尝试用它们创建旋转动画。该动画的顺序为 A B、B C、C A、A B 等等,无限重复。该列表还可以有 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,
) {
if (list.isEmpty()) return
Box(modifier = modifier) {
if (list.size == 1) {
itemContent(list.first())
return
}
var currentIndex by remember { mutableIntStateOf(0) }
val current = remember(currentIndex) { list[currentIndex] }
val next = remember(currentIndex) { list[(currentIndex + 1) % list.size] }
val scale = remember { 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 {
scaleX = 1f
scaleY = scale.value
transformOrigin = TransformOrigin(1f, 0f)
}) {
itemContent(current)
}
Box(modifier = Modifier.graphicsLayer {
scaleX = 1f
scaleY = 1 - scale.value
transformOrigin = TransformOrigin(1f, 1f)
}) {
itemContent(next)
}
}
}
我不知道你的错误到底在哪里,但这个清理后的版本现在可以按预期工作:
@Composable
fun FollowEventCardHome() {
val 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,
)
}
}
}