Android RecyclerView:实现部分滑出按钮与完全滑动删除功能
解决RecyclerView实现通知栏式滑动的问题
一、现有代码的问题分析
你当前的实现有两个核心问题:
onChildDraw里的逻辑错误:当滑动距离小于200时直接把dX设为0,导致Item根本不会滑动,自然看不到部分滑出的按钮;getSwipeThreshold返回0.1f会导致只要滑动超过Item宽度的10%就触发onSwiped,无法停留在部分滑动状态。
二、修正后的实现方案
要实现“部分滑动停住显示按钮+完全滑动触发删除”,需要控制滑动的阈值、滑动距离限制,以及手指释放后的状态处理:
1. 定义回调接口(用于外部处理操作)
interface SwipeActionCallback { fun onPartialSwipe(holder: RecyclerView.ViewHolder) // 部分滑动停住后的回调,可处理按钮点击 fun onFullSwipe(holder: RecyclerView.ViewHolder) // 完全滑动后的删除操作 }
2. 重写SwipeHelper核心逻辑
class SwipeHelper( dragDirs: Int, swipeDirs: Int, private val callback: SwipeActionCallback, private val partialSwipeMaxDistance: Int = 200 // 部分滑动的最大距离(需传入dp转px后的值) ) : ItemTouchHelper.SimpleCallback(dragDirs, swipeDirs) { private var isPartialSwipe = false override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean = false override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { if (!isPartialSwipe) { callback.onFullSwipe(viewHolder) } } // 控制滑动阈值:滑动距离小于partialSwipeMaxDistance时,设置极大值避免触发onSwiped override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float { return if (abs(viewHolder.itemView.translationX) < partialSwipeMaxDistance) { Float.MAX_VALUE } else { 0.5f // 超过部分滑动距离后,滑过半宽触发删除 } } // 提高滑动触发速度阈值,避免误操作 override fun getSwipeEscapeVelocity(defaultValue: Float): Float { return defaultValue * 1.5f } override fun onChildDraw( c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean ) { // 限制滑动距离:部分滑动阶段最多滑出partialSwipeMaxDistance val clampedDX = if (abs(dX) < partialSwipeMaxDistance) { dX } else { // 超过部分滑动距离后,允许继续滑动到删除(增加阻尼感) if (dX < 0) -partialSwipeMaxDistance - (abs(dX) - partialSwipeMaxDistance) * 0.5f else partialSwipeMaxDistance + (dX - partialSwipeMaxDistance) * 0.5f } // 绘制后台操作按钮 val paint1 = Paint().apply { color = Color.GREEN } val paint2 = Paint().apply { color = Color.BLUE } val itemView = viewHolder.itemView val buttonWidth = partialSwipeMaxDistance / 2 val rect1 = Rect( itemView.right + clampedDX.toInt(), itemView.top, itemView.right + clampedDX.toInt() + buttonWidth, itemView.bottom ) val rect2 = Rect( rect1.right, itemView.top, itemView.right, itemView.bottom ) c.drawRect(rect1, paint1) c.drawRect(rect2, paint2) // 调用父类方法绘制Item的滑动效果 super.onChildDraw(c, recyclerView, viewHolder, clampedDX, dY, actionState, isCurrentlyActive) } // 手指释放后处理状态:部分滑动停住、小距离滑动复位、大距离触发删除 override fun onInteractionEnd(viewHolder: RecyclerView.ViewHolder) { super.onInteractionEnd(viewHolder) val translationX = viewHolder.itemView.translationX isPartialSwipe = abs(translationX) in 50f..partialSwipeMaxDistance.toFloat() when { isPartialSwipe -> { // 停在部分滑动位置,显示按钮 viewHolder.itemView.animate() .translationX(-partialSwipeMaxDistance.toFloat()) .setDuration(150) .start() callback.onPartialSwipe(viewHolder) } abs(translationX) < 50f -> { // 滑动距离太小,复位 viewHolder.itemView.animate() .translationX(0f) .setDuration(150) .start() } } } }
3. 使用示例
// 先把dp转换为px val partialSwipeDistance = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 200f, resources.displayMetrics ).toInt() val swipeHelper = SwipeHelper(0, ItemTouchHelper.LEFT, object : SwipeActionCallback { override fun onPartialSwipe(holder: RecyclerView.ViewHolder) { // 这里可以给按钮绑定点击事件,比如通过holder获取自定义按钮视图 } override fun onFullSwipe(holder: RecyclerView.ViewHolder) { // 执行删除操作,比如从Adapter移除对应位置数据 adapter.removeAt(holder.adapterPosition) } }, partialSwipeDistance) ItemTouchHelper(swipeHelper).attachToRecyclerView(recyclerView)
三、Android系统通知栏的实现原理
系统通知栏的滑动并非基于RecyclerView的ItemTouchHelper,而是自定义触摸事件处理:
- 每个通知项是自定义View(如
NotificationContentView),重写onTouchEvent处理滑动手势; - 使用
Scroller或ViewPropertyAnimator实现平滑滑动动画; - 内部维护滑动状态:区分“部分滑动”和“完全滑动”的阈值,部分滑动时仅移动前景通知内容,露出下方固定的操作按钮;完全滑动时直接触发删除或其他操作;
- 布局采用“前景内容层+后台操作按钮层”的嵌套结构,滑动时仅改变前景层的位置。
内容的提问来源于stack exchange,提问作者Ashish M
相关产品推荐
相关产品推荐

