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

Jetpack Compose:如何让SwipeToDismissBox滑动时仅缩小上层容器而非滑至末端

实现Swipe to Dismiss时让容器宽度随滑动缩小而非移出屏幕

我在Jetpack Compose中实现Swipe to Dismiss功能,目前功能可正常触发关闭逻辑,但UI表现不符合预期:从右向左滑动时,希望上层卡片容器仅缩小宽度,而不是像现在这样滑动到屏幕外。

当前效果:
当前效果

期望效果:
期望效果

当前实现代码

@Composable
fun SwipeToCancel(
    closeScreen: () -> Unit,
    isExpandedScreen: Boolean
) {
    val threshold = 0.5f
    val haptic = LocalHapticFeedback.current
    var state: SwipeToDismissBoxState? = null
    state = rememberSwipeToDismissBoxState(
        positionalThreshold = {
            it * threshold
        },
        confirmValueChange = {
            haptic.performHapticFeedback(HapticFeedbackType.LongPress)
            if (it == SwipeToDismissBoxValue.EndToStart && state!!.progress > threshold) {
                closeScreen()
            }
            false
        }
    )

    SwipeToDismissBox(
        state = state,
        enableDismissFromStartToEnd = false,
        enableDismissFromEndToStart = true,
        backgroundContent = {
            val containerColor by animateColorAsState(
                if (state.targetValue == SwipeToDismissBoxValue.Settled) {
                    MaterialTheme.colorScheme.inverseSurface
                } else {
                    MaterialTheme.colorScheme.error
                }, label = ""
            )
            val contentColor by animateColorAsState(
                if (state.targetValue == SwipeToDismissBoxValue.Settled) {
                    MaterialTheme.colorScheme.inverseOnSurface
                } else {
                    MaterialTheme.colorScheme.onError
                }, label = ""
            )
            Box(
                Modifier
                    .fillMaxSize()
                    .clip(MaterialTheme.shapes.medium)
                    .background(containerColor)
            ) {
                MesIcon(
                    imageVector = Icons.Outlined.CallEnd,
                    tint = contentColor,
                    modifier = Modifier
                        .align(Alignment.CenterEnd)
                        .padding(16.dp)
                )
            }
        }
    ) {

        Card(
            modifier = Modifier
                .fillMaxWidth(if (isExpandedScreen) 0.5f else 1f),
            colors = CardDefaults.outlinedCardColors(
                containerColor = MaterialTheme.colorScheme.primaryContainer
            )
        ) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(80.dp)
                    .clip(MaterialTheme.shapes.medium)
                    .background(MaterialTheme.colorScheme.primary)
                    .padding(4.dp),
                horizontalArrangement = Arrangement.End,
                verticalAlignment = Alignment.CenterVertically
            ) {

                Text(
                    text = stringResource(id = R.string.action_slide_cancel),
                    style = MaterialTheme.typography.bodyMedium,
                    color = MaterialTheme.colorScheme.onPrimary
                )

                Spacer(modifier = Modifier.width(16.dp))

                Icon(
                    imageVector = Icons.Outlined.KeyboardDoubleArrowLeft,
                    contentDescription = stringResource(id = R.string.action_swipe_cancel),
                    tint = MaterialTheme.colorScheme.primary,
                    modifier = Modifier
                        .width(48.dp)
                        .fillMaxHeight()
                        .clip(RoundedCornerShape(12.dp))
                        .background(MaterialTheme.colorScheme.background)
                )
            }

        }

    }
}

修改方案

核心思路是利用SwipeToDismissBoxState的滑动进度值,动态调整卡片的宽度,同时抵消默认的滑动位移,让卡片保持在原位仅缩窄宽度。

修改后的完整代码:

@Composable
fun SwipeToCancel(
    closeScreen: () -> Unit,
    isExpandedScreen: Boolean
) {
    val threshold = 0.5f
    val haptic = LocalHapticFeedback.current
    val state = rememberSwipeToDismissBoxState(
        positionalThreshold = { it * threshold },
        confirmValueChange = {
            haptic.performHapticFeedback(HapticFeedbackType.LongPress)
            // 滑动进度超过阈值时触发关闭
            if (state.progress > threshold) {
                closeScreen()
            }
            false
        }
    )

    SwipeToDismissBox(
        state = state,
        enableDismissFromStartToEnd = false,
        enableDismissFromEndToStart = true,
        backgroundContent = {
            val containerColor by animateColorAsState(
                if (state.targetValue == SwipeToDismissBoxValue.Settled) {
                    MaterialTheme.colorScheme.inverseSurface
                } else {
                    MaterialTheme.colorScheme.error
                }, label = ""
            )
            val contentColor by animateColorAsState(
                if (state.targetValue == SwipeToDismissBoxValue.Settled) {
                    MaterialTheme.colorScheme.inverseOnSurface
                } else {
                    MaterialTheme.colorScheme.onError
                }, label = ""
            )
            Box(
                Modifier
                    .fillMaxSize()
                    .clip(MaterialTheme.shapes.medium)
                    .background(containerColor)
            ) {
                MesIcon(
                    imageVector = Icons.Outlined.CallEnd,
                    tint = contentColor,
                    modifier = Modifier
                        .align(Alignment.CenterEnd)
                        .padding(16.dp)
                )
            }
        }
    ) {
        Card(
            modifier = Modifier
                // 根据滑动进度动态计算卡片宽度:进度0时为原宽度,进度1时宽度为0
                .fillMaxWidth(
                    (if (isExpandedScreen) 0.5f else 1f) * (1 - state.progress)
                )
                // 抵消SwipeToDismissBox默认的滑动位移,让卡片保持在原位
                .offset(x = -state.offset.value)
                .animateContentSize(), // 添加强制动画,让宽度变化更平滑
            colors = CardDefaults.outlinedCardColors(
                containerColor = MaterialTheme.colorScheme.primaryContainer
            )
        ) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(80.dp)
                    .clip(MaterialTheme.shapes.medium)
                    .background(MaterialTheme.colorScheme.primary)
                    .padding(4.dp),
                horizontalArrangement = Arrangement.End,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    text = stringResource(id = R.string.action_slide_cancel),
                    style = MaterialTheme.typography.bodyMedium,
                    color = MaterialTheme.colorScheme.onPrimary
                )

                Spacer(modifier = Modifier.width(16.dp))

                Icon(
                    imageVector = Icons.Outlined.KeyboardDoubleArrowLeft,
                    contentDescription = stringResource(id = R.string.action_swipe_cancel),
                    tint = MaterialTheme.colorScheme.primary,
                    modifier = Modifier
                        .width(48.dp)
                        .fillMaxHeight()
                        .clip(RoundedCornerShape(12.dp))
                        .background(MaterialTheme.colorScheme.background)
                )
            }
        }
    }
}

关键改动说明

  1. 动态宽度计算:将fillMaxWidth的参数改为结合滑动进度state.progress的表达式,进度从0到1变化时,卡片宽度从原宽度线性缩小至0。
  2. 抵消滑动位移:通过offset(x = -state.offset.value)抵消SwipeToDismissBox默认的水平滑动偏移,让卡片始终保持在初始位置,只改变宽度。
  3. 平滑动画:添加animateContentSize()修饰符,确保宽度变化过程有平滑的过渡动画。
  4. 简化状态判断:直接通过state.progress判断是否达到触发关闭的阈值,逻辑更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 18:48:11