ItemTouchHelper : Limit swipe width of ItemTouchHelper.SimpleCallBack on RecyclerView ItemTouchHelper : Limit swipe width of ItemTouchHelper.SimpleCallBack on RecyclerView android android

ItemTouchHelper : Limit swipe width of ItemTouchHelper.SimpleCallBack on RecyclerView


Your onChildDraw() method at the end calls its super super method like so:

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

To limit the maximum width of your swipe you can invoke super.onChildDraw() with a reduced dX:

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


Following the response from Alexander Thiel I created a class that manages Left and Right Item Swipe:

/** * 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    )

The class is ready to use with customizable left/right icons and texts.

  • When the user swipes an item to left/right, the background is animated with an alpha animation and the OnItemSwipeListener is called.
  • The swipeableWidthPercentage param is used to determine how much width we wan't to use for drawing (0.25 by default). This answers the question.

Example usage of the listener:

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                )            }        }    }