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

Jetpack Compose中如何让内容与Motion Layout折叠顶栏联动对齐?

问题

在Jetpack Compose中使用Motion Layout实现了可折叠顶部应用栏,应用栏内的Box通过swipeable修饰符设置了锚点,对应COLLAPSED和EXPANDED两种滑动状态。当前问题是:顶部应用栏展开时,下方内容会被遮挡,需要建立内容与顶部栏的约束关系,让内容高度随滑动状态动态调整。

解决方案建议
  • 让MotionLayout接管全屏布局:将MotionLayout的修饰符从fillMaxWidth改为fillMaxSize,使其能够同时控制顶部栏和下方内容的布局,确保两者的位置关系始终正确。
  • 在MotionScene中定义约束关系:在motion_scene.xml里给下方内容(新增content布局ID)设置约束,让内容的顶部与顶部栏(box)的底部对齐。这样顶部栏高度变化时,内容会自动调整位置,不会被遮挡。
  • 用MotionLayout内置滑动替代独立swipeable:MotionLayout本身支持手势滑动控制过渡,无需额外给Box添加swipeable。在MotionScene中定义OnSwipe动作绑定到顶部栏,滑动逻辑更统一,还能避免状态不同步的问题。
  • 动态计算内容高度(备选):如果需要手动控制,可以通过swipingState的当前状态计算顶部栏高度,给下方内容设置fillMaxHeight() - 顶部栏高度,但更推荐用MotionScene的约束自动处理。
修改后的代码示例

Composable代码

@OptIn(ExperimentalMotionApi::class)
@Composable
fun UserData(user: User) {
    val context = LocalContext.current
    val motionScene = remember {
        context.resources
            .openRawResource(R.raw.motion_scene)
            .readBytes()
            .decodeToString()
    }

    MotionLayout(
        motionScene = MotionScene(content = motionScene),
        modifier = Modifier.fillMaxSize()
    ) {
        // 顶部栏容器
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .background(LightBlue)
                .layoutId("box")
        )

        // 用户头像
        val painter = rememberAsyncImagePainter(
            model = ImageRequest.Builder(LocalContext.current)
                .data(user.imageUrl)
                .build()
        )
        Image(
            painter = painter,
            contentDescription = "用户头像",
            modifier = Modifier
                .clip(CircleShape)
                .border(2.dp, Color.White, CircleShape)
                .layoutId("profile_pic"),
            contentScale = ContentScale.Crop
        )

        // 用户信息列
        Column(
            modifier = Modifier.layoutId("details"),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(
                modifier = Modifier.padding(start = 13.dp),
                text = user.email,
                fontSize = 18.sp,
                color = Color.White
            )
            Spacer(modifier = Modifier.height(5.dp))
            Text(text = "看板数量:", fontSize = 18.sp, color = Color.White)
            Spacer(modifier = Modifier.height(5.dp))
            Text(text = "卡片数量:", fontSize = 18.sp, color = Color.White)
            Spacer(modifier = Modifier.height(8.dp))
            androidx.compose.material3.Button(
                modifier = Modifier
                    .width(110.dp)
                    .padding(start = 10.dp),
                colors = ButtonDefaults.buttonColors(Color.Blue.copy(0.2f)),
                onClick = {}
            ) {
                Text(text = "退出登录", color = Color.White)
            }
        }

        // 用户名文本
        Text(
            text = user.userName,
            fontSize = 24.sp,
            color = Color.White,
            modifier = Modifier.layoutId("username")
        )

        // 下方内容区域
        Column(
            modifier = Modifier
                .layoutId("content")
                .fillMaxWidth()
                .background(Color.White),
            content = {
                // 替换为实际的下方内容
                Text(text = "下方内容展示区", fontSize = 20.sp, modifier = Modifier.padding(16.dp))
            }
        )
    }
}

MotionScene配置(res/raw/motion_scene.xml)

<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <!-- 展开到折叠的过渡动画 -->
    <Transition
        motion:constraintSetStart="@id/expanded"
        motion:constraintSetEnd="@id/collapsed"
        motion:duration="300">
        <OnSwipe
            motion:touchAnchorId="@id/box"
            motion:touchAnchorSide="top"
            motion:dragDirection="dragUp" />
    </Transition>

    <!-- 展开状态约束 -->
    <ConstraintSet android:id="@+id/expanded">
        <Constraint android:id="@id/box">
            <Layout
                android:layout_width="match_parent"
                android:layout_height="400dp" />
        </Constraint>

        <Constraint android:id="@id/profile_pic">
            <Layout
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:layout_marginTop="80dp"
                motion:layout_constraintTop_toTopOf="@id/box"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintEnd_toEndOf="parent" />
        </Constraint>

        <Constraint android:id="@id/details">
            <Layout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                motion:layout_constraintTop_toBottomOf="@id/profile_pic"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintEnd_toEndOf="parent" />
        </Constraint>

        <Constraint android:id="@id/username">
            <Layout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="20dp"
                motion:layout_constraintTop_toBottomOf="@id/details"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintEnd_toEndOf="parent" />
        </Constraint>

        <Constraint android:id="@id/content">
            <Layout
                android:layout_width="match_parent"
                android:layout_height="0dp"
                motion:layout_constraintTop_toBottomOf="@id/box"
                motion:layout_constraintBottom_toBottomOf="parent" />
        </Constraint>
    </ConstraintSet>

    <!-- 折叠状态约束 -->
    <ConstraintSet android:id="@+id/collapsed">
        <Constraint android:id="@id/box">
            <Layout
                android:layout_width="match_parent"
                android:layout_height="60dp" />
        </Constraint>

        <Constraint android:id="@id/profile_pic">
            <Layout
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_marginStart="16dp"
                motion:layout_constraintTop_toTopOf="@id/box"
                motion:layout_constraintBottom_toBottomOf="@id/box"
                motion:layout_constraintStart_toStartOf="parent" />
        </Constraint>

        <Constraint android:id="@id/details">
            <Layout
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:visibility="gone" />
        </Constraint>

        <Constraint android:id="@id/username">
            <Layout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                motion:layout_constraintTop_toTopOf="@id/box"
                motion:layout_constraintBottom_toBottomOf="@id/box"
                motion:layout_constraintStart_toEndOf="@id/profile_pic" />
        </Constraint>

        <Constraint android:id="@id/content">
            <Layout
                android:layout_width="match_parent"
                android:layout_height="0dp"
                motion:layout_constraintTop_toBottomOf="@id/box"
                motion:layout_constraintBottom_toBottomOf="parent" />
        </Constraint>
    </ConstraintSet>
</MotionScene>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 20:45:01