ItemTouchHelper:在 RecyclerView 上限制 ItemTouchHelper.SimpleCallBack 的滑动宽度

ItemTouchHelper : Limit swipe width of ItemTouchHelper.SimpleCallBack on RecyclerView

我已经成功地实现了滑动行为并用它执行了一些操作。我现在遇到的问题是我想在滑动项目时限制滑动宽度。

目前是这样

但我想将滑动行为宽度限制到这里

这是我到目前为止所做的:-

private void swipeExample(){
        ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {

            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
//                final int fromPos = viewHolder.getAdapterPosition();
//                final int toPos = target.getAdapterPosition();
                return false;
            }

            @Override
            public void onSwiped(final RecyclerView.ViewHolder viewHolder, int swipeDir) {

                // Show delete confirmation if swipped left
                if (swipeDir == ItemTouchHelper.LEFT) {

                    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                    builder.setMessage("Are you sure you want to delete?")
                            .setCancelable(false)
                            .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int id) {
                                    deleteItem(viewHolder);
//                                    getActivity().finish();
                                }
                            })
                            .setNegativeButton("No", new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int id) {
                                    dialog.cancel();
                                }
                            });
                    AlertDialog alert = builder.create();
                    alert.show();

                } else if (swipeDir == ItemTouchHelper.RIGHT) {
                    // Show edit dialog
                }
            }

            @Override
            public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {

                Bitmap icon;
                if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {

                    View itemView = viewHolder.itemView;
                    float height = (float) itemView.getBottom() - (float) itemView.getTop();
                    float width = height / 3;

                    if (dX > 0) {
                        p.setColor(Color.parseColor("#388E3C"));
                        RectF background = new RectF((float) itemView.getLeft(), (float) itemView.getTop(), dX, (float) itemView.getBottom());
                        c.drawRect(background, p);
                        icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_mode_edit_white_24dp);
                        RectF icon_dest = new RectF((float) itemView.getLeft() + width, (float) itemView.getTop() + width, (float) itemView.getLeft() + 2 * width, (float) itemView.getBottom() - width);
                        c.drawBitmap(icon, null, icon_dest, p);
                    } else if (dX < 0) {
                        p.setColor(Color.parseColor("#D32F2F"));
                        RectF background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom());
                        c.drawRect(background, p);
                        icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_delete_white_24dp);
                        RectF icon_dest = new RectF((float) itemView.getRight() - 2 * width, (float) itemView.getTop() + width, (float) itemView.getRight() - width, (float) itemView.getBottom() - width);
                        c.drawBitmap(icon, null, icon_dest, p);
                    }
                }
                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            }
        };

        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
        itemTouchHelper.attachToRecyclerView(recyclerView);

    }

请问如何限制onChildDraw()方法的宽度

提前致谢。

你的 onChildDraw() 方法最后调用它的 super super 方法如下:

super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);

要限制滑动的最大宽度,您可以调用 super.onChildDraw() 减少 dX:

super.onChildDraw(c, recyclerView, viewHolder, dX / 4, dY, actionState, isCurrentlyActive);

根据 Alexander Thiel 的回复,我创建了一个 class 来管理左右项目滑动:

/**
 * Adds [RecyclerView] items decoration shown when the user swipes an item horizontally (left or right).
 * When the item is being swiped to left/right, customizable views are shown (check MEMBERS section).
 *
 * USAGE:
 * val callback = ItemTouchSwipeHelperCallback(recyclerView)
 * callback.onItemSwipeListener = this
 * callback.rightBackgroundColor = R.color.red
 * // ... Any other type of configuration
 *
 */
class ItemTouchSwipeHelperCallback(
    private val recyclerView: RecyclerView
) : ItemTouchHelper.SimpleCallback(
    ItemTouchHelper.ACTION_STATE_IDLE,
    ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
) {
    private val context: Context
        get() = recyclerView.context

    /* ------------------- VIEW DEFAULTS ------------------- */

    private val defaultIconSizeDp = 28
    private val defaultIconHorizontalMarginDp = 30

    @ColorRes
    private val defaultRightIconTint = R.color.white_FFFFFF

    @ColorRes
    private val defaultLeftIconTint = R.color.white_FFFFFF

    @ColorRes
    private val defaultLeftBackgroundColor = R.color.red_FF7F8F

    @ColorRes
    private val defaultRightBackgroundColor = R.color.green_64ECA8

    @ColorRes
    private val defaultTextsColor = R.color.white_FFFFFF
    private val defaultLeftText = context.getString(R.string.action_left_swipe)
    private val defaultRightText = context.getString(R.string.action_right_swipe)
    private val defaultTextsTypeface = ResourcesCompat.getFont(context, R.font.roboto_bold)
    private val defaultTextsSize = context.resources.getDimensionPixelSize(R.dimen.text_size_12px)

    /* ------------------- MEMBERS ------------------- */

    /** Left and Right icons horizontal margin (left and right respectively) */
    var iconsHorizontalMargin: Int = 0
        set(value) {
            field = context.applyDimension(value).toInt()
        }

    /** Left and Right icons size. The size is applied to the drawables is width == height (square) */
    var iconsSize: Int = 0
        set(value) {
            field = context.applyDimension(value).toInt()
        }

    /** Helper member */
    private val halfIconSize: Float
        get() = iconsSize / 2f

    /** Left icon Drawable to draw */
    var leftIcon: Drawable? = ResourcesCompat.getDrawable(
        context.resources, R.drawable.ic_left_swipe, context.theme
    )?.mutate()

    /** Right icon Drawable to draw */
    var rightIcon: Drawable? = ResourcesCompat.getDrawable(
        context.resources, R.drawable.ic_right_swipe, context.theme
    )?.mutate()

    /** Left text String to draw. Positioned below leftIcon and centered horizontally. */
    var leftText: String = ""
        set(value) {
            field = value.toUpperCase()
        }

    /** Right text String to draw. Positioned below rightIcon and centered horizontally. */
    var rightText: String = ""
        set(value) {
            field = value.toUpperCase()
        }

    /** Right and Left texts typeface (font) */
    var textsTypeface: Typeface? = null
        set(value) {
            field = value
            textPaint.typeface = value
        }

    /** Right and Left texts sizes */
    var textsSize: Float = 0f
        set(value) {
            field = value
            textPaint.textSize = value
        }

    /** Left and Right texts colors */
    @ColorRes
    var textsColor: Int = 0
        set(value) {
            field = value
            textPaint.color = ContextCompat.getColor(context, value)
        }

    /** Right icon [Drawable] tint color */
    @ColorRes
    var rightIconTint: Int = 0
        set(value) {
            field = ContextCompat.getColor(context, value)
            rightIcon?.let {
                DrawableCompat.setTint(it, field)
            }
        }

    /** Left icon [Drawable] tint color */
    @ColorRes
    var leftIconTint: Int = 0
        set(value) {
            field = ContextCompat.getColor(context, value)
            leftIcon?.let {
                DrawableCompat.setTint(it, field)
            }
        }

    /** Left View Background Color shown when the user is swiping */
    @ColorRes
    var leftBackgroundColor: Int = 0
        set(value) {
            field = value
            leftPaint.color = ContextCompat.getColor(context, value)
        }

    /** Right View Background Color shown when the user is swiping */
    @ColorRes
    var rightBackgroundColor: Int = 0
        set(value) {
            field = value
            rightPaint.color = ContextCompat.getColor(context, value)
        }

    /** false for swipe to left direction disabled */
    var swipeToLeftEnabled: Boolean = true
        set(value) {
            field = value
            if (!value) {
                setDefaultSwipeDirs(ItemTouchHelper.RIGHT)
            }
        }

    /** false for swipe to right direction disabled */
    var swipeToRightEnabled: Boolean = true
        set(value) {
            field = value
            if (!value) {
                setDefaultSwipeDirs(ItemTouchHelper.LEFT)
            }
        }

    /** Max Item Width for drawing when is being swiped [0 - 1] **/
    var swipeableWidthPercentage: Float = 0.25f

    /**
     * Item swipe listener called when the user swipes the item to the left/right.
     * Notified with the item position and direction.
     *
     * Values of direction: [ItemTouchHelper.LEFT] or [ItemTouchHelper.RIGHT]
     */
    var onItemSwipeListener: OnItemSwipeListener? = null

    /* ------------------- DRAWING ------------------- */

    /** Paint used to draw the left background */
    private val leftPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)

    /** Paint used to draw the right background */
    private val rightPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)

    /** Paint used to draw the left and right texts */
    private val textPaint: Paint = Paint()

    private var bgAlphaAnimation: ValueAnimator

    private val recyclerViewAdapterDataObserver: RecyclerView.AdapterDataObserver

    private var pendingItemsAnimations: Int = 0

    init {
        iconsSize = defaultIconSizeDp
        iconsHorizontalMargin = defaultIconHorizontalMarginDp

        leftText = defaultLeftText
        rightText = defaultRightText

        leftBackgroundColor = defaultLeftBackgroundColor
        rightBackgroundColor = defaultRightBackgroundColor

        textsTypeface = defaultTextsTypeface
        textsSize = defaultTextsSize.toFloat()
        textsColor = defaultTextsColor

        rightIconTint = defaultRightIconTint
        leftIconTint = defaultLeftIconTint

        bgAlphaAnimation = ValueAnimator.ofInt(255, 255 / 2).apply {
            interpolator = AccelerateDecelerateInterpolator()
            repeatCount = ValueAnimator.INFINITE
            repeatMode = ValueAnimator.REVERSE
            duration = 500
        }

        ItemTouchHelper(this).attachToRecyclerView(recyclerView)

        recyclerViewAdapterDataObserver = object : RecyclerView.AdapterDataObserver() {
            override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
                pendingItemsAnimations -= 1
                if (pendingItemsAnimations == 0) {
                    bgAlphaAnimation.cancel()
                    bgAlphaAnimation.removeAllUpdateListeners()
                    leftPaint.alpha = 255
                    rightPaint.alpha = 255
                }
            }
        }
        recyclerView.adapter?.registerAdapterDataObserver(recyclerViewAdapterDataObserver)
    }

    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        return false
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        val position = viewHolder.absoluteAdapterPosition
        pendingItemsAnimations += 1
        if (!bgAlphaAnimation.isRunning) {
            bgAlphaAnimation.start()
        }
        onItemSwipeListener?.onItemSwiped(recyclerView, position, direction)
//        recyclerView.adapter?.notifyItemChanged(position)
    }

    override fun onChildDraw(
        c: Canvas,
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        dX: Float,
        dY: Float,
        actionState: Int,
        isCurrentlyActive: Boolean
    ) {
        super.onChildDraw(c, recyclerView, viewHolder, dX * swipeableWidthPercentage, dY, actionState, isCurrentlyActive)
        val view = viewHolder.itemView
        val yCenter = (view.top + view.bottom) / 2

        when {
            swipeToRightEnabled && dX > 0 -> {
                // Swipe to the right
                c.drawRect(
                    view.left.toFloat(),
                    view.top.toFloat(),
                    view.left.toFloat() + (dX * swipeableWidthPercentage),
                    view.bottom.toFloat(),
                    leftPaint
                )

                val leftTxtBounds = Rect()
                textPaint.getTextBounds(leftText, 0, leftText.length, leftTxtBounds)

                leftIcon?.let {
                    it.setBounds(
                        view.left + iconsHorizontalMargin,
                        yCenter - halfIconSize.toInt() - leftTxtBounds.height() / 2,
                        view.left + iconsHorizontalMargin + iconsSize,
                        yCenter + halfIconSize.toInt() - leftTxtBounds.height() / 2
                    )
                    it.draw(c)
                }

                val y = yCenter + halfIconSize + leftTxtBounds.height() / 2
                c.drawText(
                    leftText,
                    iconsHorizontalMargin + halfIconSize - leftTxtBounds.width() / 2,
                    y,
                    textPaint
                )

                if (!isCurrentlyActive && abs(dX.toInt()) == view.width) {
                    bgAlphaAnimation.addUpdateListener {
                        leftPaint.alpha = it.animatedValue as Int
                        view.requestLayout()
                    }
                }
            }
            swipeToLeftEnabled && dX < 0 -> {
                // Swipe to the left
                c.drawRect(
                    view.right.toFloat() + (dX * swipeableWidthPercentage),
                    view.top.toFloat(),
                    view.right.toFloat(),
                    view.bottom.toFloat(),
                    rightPaint
                )

                val rightTxtBounds = Rect()
                textPaint.getTextBounds(rightText, 0, rightText.length, rightTxtBounds)

                rightIcon?.let {
                    it.setBounds(
                        view.right - iconsHorizontalMargin - iconsSize,
                        yCenter - halfIconSize.toInt() - rightTxtBounds.height() / 2,
                        view.right - iconsHorizontalMargin,
                        yCenter + halfIconSize.toInt() - rightTxtBounds.height() / 2
                    )
                    it.draw(c)
                }

                val y = yCenter + halfIconSize + rightTxtBounds.height() / 2
                c.drawText(
                    rightText,
                    view.right - iconsHorizontalMargin - halfIconSize - rightTxtBounds.width() / 2,
                    y,
                    textPaint
                )

                if (!isCurrentlyActive && abs(dX.toInt()) == view.width) {
                    bgAlphaAnimation.addUpdateListener {
                        rightPaint.alpha = it.animatedValue as Int
                        recyclerView.invalidate()
                    }
                }
            }
            else -> {
                // view unSwiped
            }
        }
    }

    interface OnItemSwipeListener {
        fun onItemSwiped(recyclerView: RecyclerView, position: Int, direction: Int)
    }
}

fun Context.applyDimension(dimen: Int, unit: Int = TypedValue.COMPLEX_UNIT_DIP) =
    TypedValue.applyDimension(
        unit,
        dimen.toFloat(),
        resources.displayMetrics
    )

class 可以与可自定义的 left/right 图标和文本一起使用。

  • 当用户将项目滑动到 left/right 时,背景会使用 alpha 动画并调用 OnItemSwipeListener
  • swipeableWidthPercentage 参数用于确定我们不想用于绘图的宽度(默认为 0.25)。这回答了这个问题。

侦听器的用法示例:

override fun onItemSwiped(recyclerView: RecyclerView, position: Int, direction: Int) {
        val door = adapter.peek(position)
        when (direction) {
            ItemTouchHelper.LEFT -> {
                // TODO perform swipe to left action
                recyclerView.postDelayed(
                    {
                        Toast.makeText(
                            requireContext(),
                            getString(R.string.swipe_left_action),
                            Toast.LENGTH_SHORT
                        ).show()
                        // Reset the item to stop the animation and return to original state
                        adapter.notifyItemChanged(position)
                    },
                    2000
                )
            }
            ItemTouchHelper.RIGHT -> {
                // TODO perform swipe to right action
                recyclerView.postDelayed(
                    {
                        Toast.makeText(
                            requireContext(),
                            getString(R.string.swipe_right_action),
                            Toast.LENGTH_SHORT
                        ).show()
                        // Reset the item to stop the animation and return to original state
                        adapter.notifyItemChanged(position)
                    },
                    2000
                )
            }
        }
    }