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

Android RecyclerView:实现部分滑出按钮与完全滑动删除功能

解决RecyclerView实现通知栏式滑动的问题

一、现有代码的问题分析

你当前的实现有两个核心问题:

  1. onChildDraw里的逻辑错误:当滑动距离小于200时直接把dX设为0,导致Item根本不会滑动,自然看不到部分滑出的按钮;
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 02:59:52