You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose共享元素过渡时内部动画重置问题求解

问题:Jetpack Compose共享元素过渡触发时,内部动画被重置

问题描述

在使用Shared Element Transition时,共享元素内的无限渐变动画(绿色↔黑色)会在触发过渡(点击切换位置)时跳回初始绿色状态。尝试用moveableContentOf包裹元素,问题仍存在。

复现代码

@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

已验证的现象

  • 单独使用moveableContentOf(无共享元素过渡)时,动画不会重置,可复用Composable实例及动画状态。
  • AnimatedVisibility同样会触发该问题。
  • 结合moveableContentOf与.sharedElementWithCallerManagedVisibility()可解决问题,但在NavHost等第三方提供AnimatedVisibilityScope的场景下不可行。

原因分析

共享元素过渡触发时,原Composable实例会被销毁并重建,导致内部的rememberInfiniteTransition状态丢失,动画被迫重置。使用moveableContentOf时,若配合默认的sharedElement,可能因Composable被重复添加到布局树中,无法正确复用状态。

可行解决方案

方案1:提升动画状态至共享元素外部

将动画状态(rememberInfiniteTransition和颜色值)移到SharedTransition Composable中,通过参数传递给AnimatedBox,确保状态在过渡前后被复用:

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
private fun SharedTransition(modifier: Modifier = Modifier) {
    var inLeft by remember { mutableStateOf(true) }
    // 提升动画状态到外部,确保跨过渡复用
    val transition = 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 ->
            Box(
                contentAlignment = if (targetState) Alignment.CenterStart else Alignment.CenterEnd,
                modifier = Modifier.fillMaxWidth()
            ) {
                AnimatedBox(
                    sharedTransitionScope = this@SharedTransitionLayout,
                    animatedContentScope = this@AnimatedContent,
                    color = color
                )
            }
        }
    }
}

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
private fun AnimatedBox(
    sharedTransitionScope: SharedTransitionScope,
    animatedContentScope: AnimatedContentScope,
    color: Color,
    modifier: Modifier = Modifier
) {
    with(sharedTransitionScope) {
        Box(
            modifier
                .size(48.dp)
                .sharedElement(
                    rememberSharedContentState(key = "animated_box"),
                    animatedContentScope,
                )
                .background(color)
        )
    }
}

方案2:使用rememberSaveable保存动画进度(进阶)

如果无法提升状态,可以通过rememberSaveable结合自定义的动画进度保存逻辑,避免状态丢失。不过这种方式需要手动处理动画进度的持久化,复杂度较高,适合状态无法轻易提升的场景。

关于moveableContentOf的说明

moveableContentOf本身可以帮助复用Composable实例,但在配合sharedElement时,需要确保元素不会被重复添加到布局树中。在NavHost等场景下,由于AnimatedVisibilityScope由框架管理,无法直接使用sharedElementWithCallerManagedVisibility,此时提升状态是更通用的解决方案。

内容的提问来源于stack exchange,提问作者aruh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 20:32:21