7

I'm working with recyclerview with snaphelper. This is my recyclerview:

    final LinearLayoutManager lm = new LinearLayoutManager(getContext(), 
    LinearLayoutManager.HORIZONTAL, false);

    recyclerview.setLayoutManager(lm);

    final PagerSnapHelper snapHelper = new PagerSnapHelper();
    snapHelper.attachToRecyclerView(recyclerview);

When the user scrolls to another cell I need to do something with the new cell position.

This is what I do to get the position:

        recyclerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            if (newState != RecyclerView.SCROLL_STATE_IDLE) {
                return;
            }
            if recyclerview == null) {
                return;
            }
             //recyclerview.clearOnChildAttachStateChangeListeners();
            int firstVisible = ((LinearLayoutManager) viewerRv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
            if (firstVisible == RecyclerView.NO_POSITION) {
                return;
            }

          doSomething(firstVisible);
       }

The problem is the firstVisible var does not always give me the right position for example when i scroll from position 0 to 9 this can be the output: 0,1,2,3,4,4,5,9

Is there another way to get the right current position?

What are the best practices for that?

Yoni
  • 1,346
  • 3
  • 16
  • 38

5 Answers5

4

this my solution :

        recyclerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            final int offset = topRv.computeHorizontalScrollOffset();
            if (offset % myCellWidth == 0) {
                final int position = offset / myCellWidth ;
            }
        }
    });

this solution gives me the current position constantly

Yoni
  • 1,346
  • 3
  • 16
  • 38
3

Usage

val recyclerView: RecyclerView = findViewById(R.id.myPagerRecyclerView)
val snapHelper: SnapHelper = PagerSnapHelper()
snapHelper.atttachToRecyclerView(snapHelper)

recyclerView.addOnScrollListener(SnapOnScrollListener(snapHelper, NOTIFY_ON_SCROLL) { position ->
        // Do what you want
})

The implementation

class SnapOnScrollListener(
        private val snapHelper: SnapHelper,
        var behavior: Int = NOTIFY_ON_SCROLL,
        var onSnapPositionChangeListener: ((position: Int) -> Unit)? = null
) : RecyclerView.OnScrollListener() {

    private var snapPosition = RecyclerView.NO_POSITION

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        if (behavior == NOTIFY_ON_SCROLL) {
            dispatchSnapPositionChange(recyclerView)
        }
    }

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        if (behavior == NOTIFY_ON_SCROLL_STATE_IDLE
                && newState == RecyclerView.SCROLL_STATE_IDLE) {
            dispatchSnapPositionChange(recyclerView)
        }
    }

    private fun dispatchSnapPositionChange(recyclerView: RecyclerView) {
        val layoutManager = recyclerView.layoutManager ?: return
        val snapView = snapHelper.findSnapView(layoutManager) ?: return
        val snapPosition = layoutManager.getPosition(snapView)
        val snapPositionChanged = this.snapPosition != snapPosition
        if (snapPositionChanged) {
            onSnapPositionChangeListener?.invoke(snapPosition)
            this.snapPosition = snapPosition
        }
    }

    companion object {
        const val NOTIFY_ON_SCROLL = 0
        const val NOTIFY_ON_SCROLL_STATE_IDLE = 1
    }
}
The source and the man who deserves the credit

Detecting snap changes with Android’s RecyclerView SnapHelper by Christian Fregnam

All Іѕ Vаиітy
  • 24,861
  • 16
  • 87
  • 111
Allan Veloso
  • 5,823
  • 1
  • 38
  • 36
2

The documentation for onScrollStateChanged(int state) explain:

  • SCROLL_STATE_IDLE: No scrolling is done.

  • SCROLL_STATE_DRAGGING: The user is dragging his finger on the screen (or it is being done programatically.

  • SCROLL_STATE_SETTLING: User has lifted his finger, and the animation is now slowing down.

You get position when state is SCROLL_STATE_DRAGGING or SCROLL_STATE_SETTLING, obviously this return all of positions that recycler view has scrolled. If you want to get the current position, you should get when the recyclerview stops (newState = SCROLL_STATE_IDLE)

You could use code similar to this for control start and ends of scroll:

boolean hasStarted = (state == SCROLL_STATE_DRAGGING);
boolean hasEnded = (state == SCROLL_STATE_IDLE);

So the final code could be some like this:

     //Initialize this variable on class that initialize recyclerview
     private boolean hasStarted;

   @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {

      //Scroll is not start and user is dragging his finger  
      if(!hasStarted && newState == SCROLL_STATE_DRAGGING){
          hasStarted = true;
      }

      //If scroll starts and it finish
      if(hasStarted && newState == SCROLL_STATE_IDLE){
          //Restart variable for next iteration
          hasStarted = false;
          //Do something
          doSomething(firstVisible);
      }
   }
Suaro
  • 312
  • 2
  • 11
  • is still the same problem – Yoni Oct 10 '17 at 07:03
  • Edit your code and add the class from which you initialize the recyclerview and the modified method to see that the flow is correct. – Suaro Oct 10 '17 at 08:08
  • I'm talking about the class once it's been modified. If you implement the method onScrollStateChanged that i write and your code do same, there are two options: 1. Not initialize hasStarted correctly (hasStarted must be a class level variable, not local of onScrollStateChanged) 2. Not control onScrollStateChanged correctly. – Suaro Oct 10 '17 at 09:03
0

In your adaptor there should a method called

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
              holder.getAdapterPosition();
    }

Thanks, Anand

Anand Khinvasara
  • 618
  • 4
  • 17
  • I've tried this method but from what I've seen, the onBindViewHolder is called when the item from the list is created and not scrolled over again, so this does work when the RecyclerView is initialized however not when the item is scrolled to again. – Demonic218 May 09 '18 at 13:02
0

Tested Kotlin Code.

        binding.recyclerViewImageViewer.addOnScrollListener(object : RecyclerView.OnScrollListener() {

            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                val offset: Int = binding.recyclerViewImageViewer.computeHorizontalScrollOffset()
                var position: Float = offset.toFloat() / (binding.recyclerViewImageViewer.getChildAt(0).measuredWidth).toFloat()
                position += 0.5f
                val postInt: Int = position.toInt()
                val positionIndex = postInt + 1
                val listSize = list.size
                binding.textViewImageViewerIndex.text = "$positionIndex / $listSize"
            }
        })
All Іѕ Vаиітy
  • 24,861
  • 16
  • 87
  • 111
Suryakant Bharti
  • 673
  • 1
  • 6
  • 24