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
相关产品推荐
相关产品推荐

