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) ) } } } }
关键改动说明
- 动态宽度计算:将
fillMaxWidth的参数改为结合滑动进度state.progress的表达式,进度从0到1变化时,卡片宽度从原宽度线性缩小至0。 - 抵消滑动位移:通过
offset(x = -state.offset.value)抵消SwipeToDismissBox默认的水平滑动偏移,让卡片始终保持在初始位置,只改变宽度。 - 平滑动画:添加
animateContentSize()修饰符,确保宽度变化过程有平滑的过渡动画。 - 简化状态判断:直接通过
state.progress判断是否达到触发关闭的阈值,逻辑更简洁。
内容的提问来源于stack exchange,提问作者Mervin Hemaraju
相关产品推荐
相关产品推荐

