Jetpack Compose不依赖ConstraintLayout实现Row转Column布局动画
解决方案
你原有代码的问题在于,两个独立的OneLayout和TwoLayout被视为完全不相关的布局树,Compose无法识别不同状态下的红色盒子、文本是同一个元素,因此只会触发默认的淡入淡出过渡,不会执行位移动画。可以通过以下两种方案实现需求,均不依赖ConstraintLayout:
方案一:合并布局动态计算(无额外API依赖,推荐)
将两个布局的逻辑合并为同一个Layout,通过动画值动态修改元素的位置参数,代码如下:
enum class ContentState { One, Two, } @Composable fun ContentAnimator( modifier: Modifier = Modifier, state: ContentState = ContentState.One, ) { // 动画化元素间距 val arrangementSpace by animateDpAsState( targetValue = if (state == ContentState.One) 16.dp else 8.dp, label = "arrangement_space" ) val isHorizontal = state == ContentState.One Box( modifier = modifier .fillMaxWidth() .animateContentSize(), // 自动动画化布局尺寸变化 contentAlignment = Alignment.Center ) { Layout( content = { Box( modifier = Modifier .size(48.dp) .background(Color.Red) .key("red_box") // 固定元素标识 ) Text( text = "Sample text", style = MaterialTheme.typography.h3, modifier = Modifier.key("sample_text") // 固定元素标识 ) }, modifier = Modifier.fillMaxWidth() ) { measurables, constraints -> val boxPlaceable = measurables[0].measure(constraints) val textPlaceable = measurables[1].measure(constraints) val layoutWidth = constraints.maxWidth val layoutHeight = if (isHorizontal) { maxOf(boxPlaceable.height, textPlaceable.height) } else { boxPlaceable.height + arrangementSpace.roundToPx() + textPlaceable.height } layout(layoutWidth, layoutHeight) { if (isHorizontal) { // Row状态:横向排列 val totalContentWidth = boxPlaceable.width + arrangementSpace.roundToPx() + textPlaceable.width val startOffset = (layoutWidth - totalContentWidth) / 2 val boxY = (layoutHeight - boxPlaceable.height) / 2 boxPlaceable.placeRelative(startOffset, boxY) val textX = startOffset + boxPlaceable.width + arrangementSpace.roundToPx() val textY = (layoutHeight - textPlaceable.height) / 2 textPlaceable.placeRelative(textX, textY) } else { // Column状态:纵向排列 val boxX = (layoutWidth - boxPlaceable.width) / 2 boxPlaceable.placeRelative(boxX, 0) val textX = (layoutWidth - textPlaceable.width) / 2 val textY = boxPlaceable.height + arrangementSpace.roundToPx() textPlaceable.placeRelative(textX, textY) } } } } }
这种方案兼容性强,性能更高,适配所有Compose稳定版本。
方案二:共享元素过渡(Compose 1.7+)
如果需要保留两个独立Layout的代码结构,可以使用Compose 1.7版本后自带的共享元素API,给相同元素绑定同一个标识key即可自动实现位置、大小的平滑过渡,代码如下:
enum class ContentState { One, Two, } @OptIn(ExperimentalSharedTransitionApi::class, ExperimentalAnimationApi::class) @Composable fun ContentAnimator( modifier: Modifier = Modifier, state: ContentState = ContentState.One, ) { Box( modifier = modifier, contentAlignment = Alignment.Center, ) { SharedTransitionLayout { AnimatedContent( targetState = state, label = "layout_switch" ) { targetState -> when (targetState) { ContentState.One -> OneLayout(this@SharedTransitionLayout) ContentState.Two -> TwoLayout(this@SharedTransitionLayout) } } } } } @OptIn(ExperimentalSharedTransitionApi::class) @Composable fun OneLayout(sharedScope: SharedTransitionScope) { with(sharedScope) { Row( verticalAlignment = Alignment.CenterVertically, ) { Box( modifier = Modifier .size(width = 48.dp, height = 48.dp) .background(Color.Red) .sharedElement( state = rememberSharedContentState(key = "red_box"), animatedVisibilityScope = this@AnimatedContent ) ) Spacer(modifier = Modifier.width(16.dp)) Text( text = "Sample text", style = MaterialTheme.typography.h3, modifier = Modifier.sharedElement( state = rememberSharedContentState(key = "sample_text"), animatedVisibilityScope = this@AnimatedContent ) ) } } } @OptIn(ExperimentalSharedTransitionApi::class) @Composable fun TwoLayout(sharedScope: SharedTransitionScope) { with(sharedScope) { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { Box( modifier = Modifier .size(width = 48.dp, height = 48.dp) .background(Color.Red) .sharedElement( state = rememberSharedContentState(key = "red_box"), animatedVisibilityScope = this@AnimatedContent ) ) Spacer(modifier = Modifier.height(8.dp)) Text( text = "Sample text", style = MaterialTheme.typography.h3, modifier = Modifier.sharedElement( state = rememberSharedContentState(key = "sample_text"), animatedVisibilityScope = this@AnimatedContent ) ) } } }
这种方案代码结构更清晰,适合复杂的多状态布局切换场景。
内容的提问来源于stack exchange,提问作者Francesc
相关产品推荐
相关产品推荐

