There's no way around a custom SnapHelper. Here is a class you can copy to your project.
The solution
class ControllableSnapHelper(val onSnapped: ((Int) -> Unit)? = null) : LinearSnapHelper() {
var snappedPosition = 0
private var snapToNext = false
private var snapToPrevious = false
var recyclerView: RecyclerView? = null
override fun attachToRecyclerView(recyclerView: RecyclerView?) {
super.attachToRecyclerView(recyclerView)
this.recyclerView = recyclerView
}
override fun findTargetSnapPosition(layoutManager: RecyclerView.LayoutManager, velocityX: Int, velocityY: Int): Int {
if (snapToNext) {
snapToNext = false
snappedPosition = Math.min(recyclerView?.adapter?.itemCount ?: 0, snappedPosition + 1)
} else if (snapToPrevious) {
snapToPrevious = false
snappedPosition = Math.max(0, snappedPosition - 1)
} else {
snappedPosition = super.findTargetSnapPosition(layoutManager, velocityX, velocityY)
}
onSnapped?.invoke(snappedPosition)
return snappedPosition
}
fun snapToNext() {
snapToNext = true
onFling(Int.MAX_VALUE, Int.MAX_VALUE)
}
fun snapToPrevious() {
snapToPrevious = true
onFling(Int.MAX_VALUE, Int.MAX_VALUE)
}
}
You can replace LinearSnapHelper
with PagerSnapHelper
as well. Simply call snapToNext()
or snapToPrevious()
. I also implemented a callback which is passed in the constructor.
How it works
We simply overwrite the findTargetSnapPosition(...)
method. This method can return any position and the SnapHelper
implementation will then compute the scroll animation to get there. The functions snapToNext()
and snapToPrevious()
simply set a boolean which tells us to snap to the next or previous view the next time findTargetSnapPosition(...)
is called. It would also be very simple to add a snapTo(index: Int)
function. After setting the boolean, we need to start the snap progress. To do so, we call onFling(...)
which would be called when the user moved the RecyclerView
. We need to pass in a value which exceeds the minimum velocity set by SnapHelper
, so we just use Int.MAX_VALUE
. As we never call super.findTargetSnapPosition(...)
, the actual velocity used by PagerSnapHelper
doesn't matter.