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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 16:54:00