1

I have followed this Stack Overflow post to create a recyclerview with viewpager behavior.

Works fine but I need to trigger the scroll to next item when user lift the finger of the screen. Right now only snap to next item when it's 50% visible.

UPDATE WITH SOLUTION

The Crysxd response was the key. To detect the snap direction I added this code at the top of the findTargetSnapPosition function:

if (velocityY < 0)
        snapToPrevious = true
else
        snapToNext = true
Sami Issa
  • 1,695
  • 1
  • 18
  • 29
  • make your own custom `LinearSnapHelper` class, see: https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v7/recyclerview/src/main/java/android/support/v7/widget/LinearSnapHelper.java#43 – pskink Feb 23 '18 at 08:43
  • There aren't a simplest solution? :) – Sami Issa Feb 23 '18 at 09:58

1 Answers1

2

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.

crysxd
  • 3,177
  • 20
  • 32
  • Thank you for your response. I'll check asap and let you know!! – Sami Issa Jul 16 '18 at 06:46
  • Please let me know if you have issues, I can then update my repsonse – crysxd Jul 16 '18 at 11:19
  • Hi Crysxd!! Works very well. Only I added this peace of code to detect the snap direction: if (velocityY < 0) snapToPrevious = true else snapToNext = true Thanks a lot for your help!! – Sami Issa Aug 09 '18 at 12:33