共享元素转换:共享元素的运行动画在转换时重置

问题描述 投票:0回答:1

我目前正在使用 Jetpack Compose 中的共享元素转换,并遇到了问题。

问题: 当共享元素转换发生时,共享元素内的运行动画将被重置。

重现代码:

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
private fun SharedTransition(modifier: Modifier = Modifier) {
    var inLeft by remember { mutableStateOf(true) }

    SharedTransitionLayout(modifier) {
        AnimatedContent(
            modifier = Modifier.clickable { inLeft = !inLeft },
            targetState = inLeft
        ) { targetState ->
            if (targetState) {
                Box(
                    contentAlignment = Alignment.CenterStart,
                    modifier = Modifier.fillMaxWidth()
                ) {
                    AnimatedBox(
                        sharedTransitionScope = this@SharedTransitionLayout,
                        animatedContentScope = this@AnimatedContent,
                    )
                }
            } else {
                Box(
                    contentAlignment = Alignment.CenterEnd,
                    modifier = Modifier.fillMaxWidth()
                ) {
                    AnimatedBox(
                        sharedTransitionScope = this@SharedTransitionLayout,
                        animatedContentScope = this@AnimatedContent,
                    )
                }
            }
        }
    }
}

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
private fun AnimatedBox(
    sharedTransitionScope: SharedTransitionScope,
    animatedContentScope: AnimatedContentScope,
    modifier: Modifier = Modifier
) {
    with(sharedTransitionScope) {
        val transition = rememberInfiniteTransition()
        val color by transition.animateColor(
            initialValue = Color.Green,
            targetValue = Color.Black,
            animationSpec = infiniteRepeatable(
                tween(durationMillis = 1500, easing = LinearEasing),
                RepeatMode.Reverse,
            ),
        )

        Box(
            modifier
                .size(BoxSize)
                .sharedElement(
                    rememberSharedContentState(key = "animated_box"),
                    animatedContentScope,
                )
                .background(color)
        )
    }
}

private val BoxSize = 48.dp

这会导致:

Video of an animation in a shared element being reset when shared element transition is performed

动画应在绿色和黑色之间来回缓慢褪色。但是当通过点击触发过渡时,动画会跳转到最初的亮绿色。 当盒子位于右侧时,在视频中可以最好地看到这一点。

我希望动画不会因转换而重新启动。

我尝试将动画元素包装在

movableContentOf { }
中,但结果是相同的:

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
private fun MovableContentSharedTransition(modifier: Modifier = Modifier) {
    val animatedBox =
        remember {
            movableContentWithReceiverOf<SharedTransitionScope, AnimatedContentScope> { animatedContentScope ->
                val transition = rememberInfiniteTransition()
                val color by transition.animateColor(
                    initialValue = Color.Magenta,
                    targetValue = Color.Black,
                    animationSpec = infiniteRepeatable(
                        tween(durationMillis = 1500, easing = LinearEasing),
                        RepeatMode.Reverse,
                    ),
                )

                Box(
                    Modifier
                        .size(48.dp)
                        .sharedElement(
                            rememberSharedContentState(key = "animated_box"),
                            animatedContentScope,
                        )
                        .background(color)
                )
            }
        }

    var inLeft by remember { mutableStateOf(true) }

    SharedTransitionLayout {
        AnimatedContent(
            modifier = modifier.clickable { inLeft = !inLeft },
            targetState = inLeft
        ) { targetState ->
            if (targetState) {
                Box(
                    contentAlignment = Alignment.CenterStart,
                    modifier = Modifier.fillMaxWidth()
                ) {
                    animatedBox(this@SharedTransitionLayout, this@AnimatedContent)
                }
            } else {
                Box(
                    contentAlignment = Alignment.CenterEnd,
                    modifier = Modifier.fillMaxWidth()
                ) {
                    animatedBox(this@SharedTransitionLayout, this@AnimatedContent)
                }
            }
        }
    }
}

撰写BOM版本:

"androidx.compose:compose-bom:2024.09.02"

有没有办法让动画继续运行?这可以用

moveableContentOf { }
解决吗?


完整代码:https://github.com/aruh/shared-element-transition-animation-reset-sample

android android-jetpack-compose shared-element-transition
1个回答
0
投票

这是因为

AnimatedContent
的工作原理。它显示状态之间的可组合项,并在转换结束时从组合中删除一个可组合项。

您正在向合成添加新的

AnimatedBox
可组合项,因此每次
InfiniteTransition
进入合成时都会生成新的
AnimatedBox
和颜色。

如果您希望颜色持续存在,只需将其移出 AnimatedBox 并通过 InfiniteTransition 将其作为参数从父级传递。

enter image description here

@Preview
@Composable
 fun SharedTransition() {
    var inLeft by remember { mutableStateOf(true) }

    val transition: InfiniteTransition = rememberInfiniteTransition()
    val color by transition.animateColor(
        initialValue = Color.Green,
        targetValue = Color.Black,
        animationSpec = infiniteRepeatable(
            tween(durationMillis = 1500, easing = LinearEasing),
            RepeatMode.Reverse,
        ),
    )

    SharedTransitionLayout(Modifier) {
        AnimatedContent(
            modifier = Modifier.clickable { inLeft = !inLeft },
            targetState = inLeft
        ) { targetState ->
            if (targetState) {
                Box(
                    contentAlignment = Alignment.CenterStart,
                    modifier = Modifier.fillMaxWidth()
                ) {
                    AnimatedBox(
                        sharedTransitionScope = this@SharedTransitionLayout,
                        animatedContentScope = this@AnimatedContent,
                        color = color
                    )
                }
            } else {
                Box(
                    contentAlignment = Alignment.CenterEnd,
                    modifier = Modifier.fillMaxWidth()
                ) {
                    AnimatedBox(
                        sharedTransitionScope = this@SharedTransitionLayout,
                        animatedContentScope = this@AnimatedContent,
                        color = color
                    )
                }
            }
        }
    }
}

源代码

// TODO: remove screen as soon as they are animated out
val currentlyVisible = remember(this) { mutableStateListOf(currentState) }
val contentMap = remember(this) { mutableScatterMapOf<S, @Composable() () -> Unit>() }
// This is needed for tooling because it could change currentState directly,
// as opposed to changing target only. When that happens we need to clear all the
// visible content and only display the content for the new current state and target state.
if (!currentlyVisible.contains(currentState)) {
    currentlyVisible.clear()
    currentlyVisible.add(currentState)
}
if (currentState == targetState) {
    if (currentlyVisible.size != 1 || currentlyVisible[0] != currentState) {
        currentlyVisible.clear()
        currentlyVisible.add(currentState)
    }
    if (contentMap.size != 1 || contentMap.containsKey(currentState)) {
        contentMap.clear()
    }
    // TODO: Do we want to support changing contentAlignment amid animation?
    rootScope.contentAlignment = contentAlignment
    rootScope.layoutDirection = layoutDirection
}
© www.soinside.com 2019 - 2024. All rights reserved.