根据 Alexander Thiel 的回复,我创建了一个管理左右项目滑动的类:
/**
* 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
)
该类已准备好与可自定义的左/右图标和文本一起使用。
- 当用户向左/向右滑动项目时,背景会使用 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
)
}
}
}