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

使用Offset Modifier时如何保持Jetpack Compose中Box的尺寸?

解决Jetpack Compose中黑色Box偏移时宽度缩小的问题

我需要实现一个黑色Box水平移动的动画:点击第二个按钮增加X轴偏移量,点击第一个按钮恢复初始状态。但使用offset修饰符后,Box的起始侧移动而末端不动,导致宽度缩小而非整体移动,该如何解决?

原始代码:

@Composable
fun Screen() {
    val offsetX = remember { Animatable(0f) }
    val coroutineScope = rememberCoroutineScope()

    Column(
        Modifier
            .fillMaxSize()
            .padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Box(
            modifier = Modifier
                .height(40.dp)
                .fillMaxWidth()
                .clip(CircleShape)
                .background(Color.White)
                .padding(horizontal = 4.dp, vertical = 2.dp),
        ) {
            Box(
                modifier = Modifier
                    .size(100.dp, 40.dp)
                    .clip(CircleShape)
                    .offset(x = offsetX.value.dp)
                    .background(Color.Black, CircleShape),
            )
        }

        Button(onClick = {
            coroutineScope.launch {
                offsetX.animateTo(0f, tween(400))
            }
        }) {
            Text("reset")
        }

        Button(onClick = {
            coroutineScope.launch {
                offsetX.animateTo(80f, tween(400))
            }
        }) {
            Text("80")
        }
    }
}

初始与偏移后对比:
初始位置
偏移后位置


之所以会出现这个问题,核心原因是父Box的clip(CircleShape)会裁剪掉超出自身边界的子元素,黑色Box向右偏移后右侧被截断,看起来就像宽度缩小了。以下是两种修复方法:

方法一:调整父Box的背景设置(推荐)

移除父Box单独的clip(CircleShape),直接把圆形Shape传给background方法,这样既能保留白色圆形背景,又不会裁剪子元素。同时优化子Box的修饰符顺序,移除多余的clip(background已自带Shape):

@Composable
fun Screen() {
    val offsetX = remember { Animatable(0f) }
    val coroutineScope = rememberCoroutineScope()
    val density = LocalDensity.current // 确保dp与像素转换正确

    Column(
        Modifier
            .fillMaxSize()
            .padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Box(
            modifier = Modifier
                .height(40.dp)
                .fillMaxWidth()
                .background(Color.White, CircleShape) // 直接设置圆形背景,替代clip+background
                .padding(horizontal = 4.dp, vertical = 2.dp),
        ) {
            Box(
                modifier = Modifier
                    .size(100.dp, 40.dp)
                    .offset(x = with(density) { offsetX.value.toDp() })
                    .background(Color.Black, CircleShape) // 移除多余的clip
            )
        }

        Button(onClick = {
            coroutineScope.launch {
                offsetX.animateTo(0f, tween(400))
            }
        }) {
            Text("reset")
        }

        Button(onClick = {
            coroutineScope.launch {
                offsetX.animateTo(80f, tween(400))
            }
        }) {
            Text("80")
        }
    }
}

方法二:使用layout修饰符控制布局位置

如果需要更灵活的布局控制,可以用layout修饰符直接修改子Box的布局位置,这样子元素的整体布局空间会跟着偏移,从根源避免裁剪问题:

@Composable
fun Screen() {
    val offsetX = remember { Animatable(0f) }
    val coroutineScope = rememberCoroutineScope()

    Column(
        Modifier
            .fillMaxSize()
            .padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Box(
            modifier = Modifier
                .height(40.dp)
                .fillMaxWidth()
                .clip(CircleShape)
                .background(Color.White)
                .padding(horizontal = 4.dp, vertical = 2.dp),
        ) {
            Box(
                modifier = Modifier
                    .size(100.dp, 40.dp)
                    .layout { measurable, constraints ->
                        val placeable = measurable.measure(constraints)
                        // 直接调整布局位置
                        layout(placeable.width, placeable.height) {
                            placeable.place(offsetX.value.roundToInt(), 0)
                        }
                    }
                    .background(Color.Black, CircleShape)
            )
        }

        Button(onClick = {
            coroutineScope.launch {
                offsetX.animateTo(0f, tween(400))
            }
        }) {
            Text("reset")
        }

        Button(onClick = {
            coroutineScope.launch {
                offsetX.animateTo(80f, tween(400))
            }
        }) {
            Text("80")
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 22:09:57