如何在Material3 Compose中实现Swipeable滑动交互效果
M3 移除旧版Modifier.swipeable和配套的SwipeableState不是砍掉滑动交互能力,而是把原来耦合在Material库中的滑动逻辑拆成了分层的API,适配不同场景的使用需求,不需要找第三方库就能实现和旧版一致甚至更灵活的效果,常用方案按场景选就行:
1. 自定义滑动锚点场景:使用基础API anchoredDraggable
这个是旧版swipeable的直接替代,属于Compose Foundation层提供的底层能力,不绑定Material组件,所有需要自定义滑动锚点、吸附阈值、状态监听的通用滑动场景都可以用,灵活度最高。
核心实现逻辑:
- 先定义滑动状态枚举,对应不同的锚点位置,比如横向滑动就定义「关闭」「左滑露出操作区」「右滑露出操作区」几个状态
- 用
rememberAnchoredDraggableState初始化滑动状态,配置初始值、位置吸附阈值、速度触发阈值、滑动动画参数 - 给状态绑定锚点和对应偏移值的映射关系
- 给目标组件加
anchoredDraggable修饰符绑定状态、指定滑动方向,再通过offset修饰符把状态的偏移量应用到组件上即可
最简实现代码参考:
// 定义滑动锚点状态 enum class HorizontalSwipeState { Closed, EndOpened } @Composable fun CustomSwipeableItem() { val density = LocalDensity.current // 左滑后露出的操作区宽度 val actionAreaWidth = with(density) { 88.dp.toPx() } // 初始化滑动状态 val swipeState = rememberAnchoredDraggableState( initialValue = HorizontalSwipeState.Closed, // 滑动超过一半距离就吸附到下一个锚点 positionalThreshold = { fullDistance -> fullDistance * 0.5f }, // 滑动速度超过阈值直接吸附,不用等位置达标 velocityThreshold = { with(density) { 120.dp.toPx() } }, animationSpec = spring(stiffness = Spring.StiffnessMediumLow) ).apply { // 绑定锚点和对应偏移值 updateAnchors( DraggableAnchors { HorizontalSwipeState.Closed at 0f HorizontalSwipeState.EndOpened at -actionAreaWidth } ) } Box(modifier = Modifier.fillMaxWidth().height(64.dp)) { // 底层:滑动后露出的操作区域 Row( modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), verticalAlignment = Alignment.CenterVertically ) { IconButton(onClick = { /* 删除逻辑 */ }) { Icon(Icons.Default.Delete, contentDescription = "删除") } } // 上层:可滑动的主内容 Surface( modifier = Modifier .fillMaxSize() .offset { IntOffset(x = swipeState.offset.roundToInt(), y = 0) } .anchoredDraggable( state = swipeState, orientation = Orientation.Horizontal ), shadowElevation = 1.dp ) { Text("滑动我试试", modifier = Modifier.padding(16.dp)) } } }
如果需要和列表做嵌套滑动,给Modifier追加nestedScroll(swipeState.nestedScrollConnection)即可,官方已经封装好了嵌套滑动的处理逻辑,比旧版swipeable的嵌套适配简单很多。
2. 列表项滑动删除/滑动操作场景:直接用封装好的SwipeToDismiss组件
如果是做消息列表、待办列表这类常见的滑动删除、滑动标记已读场景,不用自己用底层API搭,M3已经封装好了现成的SwipeToDismiss组件,自带列表复用适配、无障碍支持、滑动冲突处理:
- 用
rememberDismissState初始化组件状态,可自定义滑动方向、不同方向的阈值 - 通过
background参数自定义滑动过程中露出的背景、操作按钮 - 监听
dismissState.currentValue就能拿到当前滑动状态,触发对应业务逻辑
这个组件是M3专门针对列表滑动场景优化的,稳定性比自己写的自定义逻辑好很多。
3. 抽屉、底部弹窗这类系统组件滑动:直接用内置滑动能力
M3提供的ModalNavigationDrawer(侧边模态抽屉)、ModalBottomSheet(模态底部弹窗)、DismissibleDrawerSheet等组件本身已经内置了完整的滑动交互支持,不需要额外加滑动修饰符,直接通过组件参数配置是否允许手势关闭、滑动阈值、动画参数即可。
旧项目迁移提示
如果是从M2版本迁移到M3的存量项目,不想一次性重写所有swipeable逻辑,可以临时用兼容依赖过渡,但长期还是建议切换到上述原生API,旧API后续不会再做性能优化和bug修复,长期维护成本更高。
踩坑提示:不要直接照搬M2版本SwipeableState的偏移计算逻辑,M3的手势管线做了重构,旧的阈值计算、偏移映射逻辑直接套用到新API上会出现跟手性差、吸附位置不准的问题。
内容的提问来源于stack exchange,提问作者אריאל עדן

