50

In my RecyclerView I have some items that user can scroll and see that. Now I want to save this position and scroll that after come back. This below code return 0 always and I can't save that:

recyclerMarketLists.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
    @Override
    public void onScrollChanged() {
        recyclerViewCurrentScrolledPosition = recyclerMarketLists.getScrollY();
        Log.e("Y: ", recyclerViewCurrentSceolledPosition + "");
    }
});

the Logcat:

07-07 18:28:30.919 2124-2124/com.sample.presentationproject E/Y:: 0
peterh
  • 11,875
  • 18
  • 85
  • 108
DolDurma
  • 15,753
  • 51
  • 198
  • 377

10 Answers10

68

You are trying to get the info on the wrong object. It is not the RecyclerView nor the Adapter responsibility but the RecyclerView's LayoutManager.

Instead of the generic ViewTreeObserver.OnScrollChangedListener() I would recommend to add instead the RecyclerView.OnScrollListener and use the onScrollStateChanged(RecyclerView recyclerView, int newState) callback which gives you the newState, you should use SCROLL_STATE_IDLE to fetch its position. Meaning:

yourRecyclerView.getLayoutManager().findFirstVisibleItemPosition();

As Rik van Velzen pointed out, you probably need to cast your LayoutManager to a LinearLayoutManager or GridLayoutManager (you have to cast to the correct type you are using) to access these findVisibleXXXX() methods.

On said callback method. Hope I made this clear enough for your, you can find documentation on the classes here:

RecyclerView.OnScrollListener

yigit's (Google) response on visible positions

Joaquim Ley
  • 4,038
  • 2
  • 24
  • 42
34

Thanks for solution Joaquim Ley. This helps create recyclerView horizontal pager without using any library.

Init recyclerView

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_vocabulary_list);

    // Set up the recyclerView with the sections adapter.
    mRecyclerView = findViewById(R.id.list);
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
    mRecyclerView.setAdapter(new VocabularyListAdapter<>(vocabularyList));
    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == RecyclerView.SCROLL_STATE_IDLE){
                int position = getCurrentItem();
                onPageChanged(position);
            }
        }
    });
    PagerSnapHelper snapHelper = new PagerSnapHelper();
    snapHelper.attachToRecyclerView(mRecyclerView);
}



public boolean hasPreview() {
   return getCurrentItem() > 0;
}

public boolean hasNext() {
    return mRecyclerView.getAdapter() != null &&
            getCurrentItem() < (mRecyclerView.getAdapter().getItemCount()- 1);
}

public void preview() {
    int position = getCurrentItem();
    if (position > 0)
        setCurrentItem(position -1, true);
}

public void next() {
    RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
    if (adapter == null)
        return;

    int position = getCurrentItem();
    int count = adapter.getItemCount();
    if (position < (count -1))
        setCurrentItem(position + 1, true);
}

private int getCurrentItem(){
    return ((LinearLayoutManager)mRecyclerView.getLayoutManager())
            .findFirstVisibleItemPosition();
}

private void setCurrentItem(int position, boolean smooth){
    if (smooth)
        mRecyclerView.smoothScrollToPosition(position);
    else
        mRecyclerView.scrollToPosition(position);
}
Vahe Gharibyan
  • 5,277
  • 4
  • 36
  • 47
18

This is the extension function in Kotlin:

fun RecyclerView?.getCurrentPosition() : Int {
    return (this?.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
}

You can use it just invoking this function on your RecyclerView:

val position = yourRecyclerView.getCurrentPosition()
Mattia Ferigutti
  • 2,608
  • 1
  • 18
  • 22
  • 3
    its not advisiable to direct cast via `as` and use `!!` . instead, nullable casting via `as?` followed by elvis (`?:0`) is much safer – ansh sachdeva May 11 '21 at 16:27
5

Try this in Kotlin:

myRecyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        val offset: Int = myRecyclerView.computeHorizontalScrollOffset()
        if (offset % myRecyclerView.width == 0) {
            val position: Int = offset / myRecyclerView.width
            Log.e("Current position is", position.toString())
        }
    }
})
reza_khalafi
  • 6,230
  • 7
  • 56
  • 82
3

I made 7 changes in my MainActivity and got pixel-perfect results. My recyclerview ALWAYS remembers its previous state when returning to it from some other Activity (e.g. DetailActivity).

Step 1: Initialize the layoutManager like so:

    // Initialize the layout manager
    moviePosterLayoutManager = (moviePosterRecyclerView.getLayoutManager() == null) ?
    new GridLayoutManager(this, numberOfColumns) :  moviePosterRecyclerView.getLayoutManager();
            // create a new layoutManager                       // reuse the existing layoutManager

Step 2: Inside your MainActivity class, create a STATIC variable to hold your layout manager state as you transition between activities:

private static Parcelable layoutManagerState;

Step 3: Also create this constant inside of the MainActivity () as well:

public static final String KEY_LAYOUT_MANAGER_STATE = "layoutManagerState";

Step 4: Inside the onCreate method for your MainActivity perform the following test, (i.e.

protected void onCreate(@Nullable final Bundle savedInstanceState) {....

    if (layoutManagerState != null) {
        //moviePosterLayoutManager
        moviePosterLayoutManager.onRestoreInstanceState(layoutManagerState);
        moviePosterRecyclerView.setLayoutManager(moviePosterLayoutManager);

    } else {

        moviePosterRecyclerView.setLayoutManager(moviePosterLayoutManager);
        moviePosterRecyclerView.scrollToPosition(moviePosition);
                                 // the default position

    }

Step 5: In your onSaveInstanceState method, be sure to save your layoutManagerState as a parcelable like so:

protected void onSaveInstanceState(Bundle outState) {

   layoutManagerState = moviePosterLayoutManager.onSaveInstanceState();

    // Save currently selected layout manager.            
   outState.putParcelable(KEY_LAYOUT_MANAGER_STATE,layoutManagerState);
}

Step 6: Place similar code in the onResume() method of MainActivity:

protected void onResume() {
    super.onResume();


    if (layoutManagerState != null) {
        moviePosterLayoutManager.onRestoreInstanceState(layoutManagerState);
    }

}

Step 7: Remember that all of this code was placed inside of my MainActivity.java file It is important to remember that the layoutManager is responsible for maintaining the scroll position of the recycler view. So, saving the state of the layoutManager is of paramount importance. The code could modularized and placed inside a custom recyclerview, but I found this to be simpler for me to implement.

P Toles
  • 29
  • 3
2

For a similar requirement plus some action needed to be performed on scroll state changed, I did this with a custom scroll listener:

class OnScrollListener(private val action: () -> Unit) : RecyclerView.OnScrollListener() {

private var actionExecuted: Boolean = false
private var oldScrollPosition: Int = 0

override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
    super.onScrollStateChanged(recyclerView, newState)
    when (newState) {
        RecyclerView.SCROLL_STATE_DRAGGING -> actionExecuted = false
        RecyclerView.SCROLL_STATE_SETTLING -> actionExecuted = false
        RecyclerView.SCROLL_STATE_IDLE -> {

            val scrolledPosition =
                (recyclerView.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition() ?: return

            if (scrolledPosition != RecyclerView.NO_POSITION && scrolledPosition != oldScrollPosition && !actionExecuted) {
                action.invoke()
                actionExecuted = true
                oldScrollPosition = scrolledPosition
            }

        }
    }
}

}

This is because of the pre-existing bug with RecyclerView onScroll which happens to trigger the callback 2-3 or even more times. And if there is some action to be performed, that will end up executing multiple times.

theThapa
  • 581
  • 4
  • 11
  • Can you explain where and how to use this class, plz? TNX. – Reyhane Farshbaf Sep 01 '20 at 11:45
  • 1
    @ReyhaneFarshbaf You can use this class where you use the recycler view e.g. in Fragment or Activity. You can create a new instance of the `ScrollListener` by passing an action lambda. Then attach it to the recycler view like: `recyclerView.addOnScrollListener(scrollListener)` – theThapa Sep 10 '20 at 14:39
1
    learnRecycleView
        .addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView,
                                   int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                int offset=learnRecycleView.computeHorizontalScrollOffset();

                if (offset % learnRecycleView.getWidth() == 0) {
                    int position = offset / learnRecycleView.getWidth();
                    System.out.println("Current position is"+position);
                }
            }
        });
Sheetal Shinde
  • 489
  • 5
  • 7
1

None of the solutions worked, I have achieved this by using layout manager. Here is the Extension function for recyclerview

fun RecyclerView.addScrollListener(onScroll: (position: Int) -> Unit) {
    var lastPosition = 0
    addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            if (layoutManager is LinearLayoutManager) {
                val currentVisibleItemPosition =
                    (layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()

                if (lastPosition != currentVisibleItemPosition && currentVisibleItemPosition != RecyclerView.NO_POSITION) {
                    onScroll.invoke(currentVisibleItemPosition)
                    lastPosition = currentVisibleItemPosition
                }
            }
        }
    })
}

You can use it like this on recyclerview.

 campusFeedRv.addScrollListener { position: Int ->
            Log.d(TAG, "Current Position: $position ")
  }
Happy Singh
  • 1,334
  • 9
  • 23
1

In my case, I need my current position view in Activity so I create a call-back method and using the below method.

Add this code in the Adapter class to get the current position of view.

Add this in onBindViewHolder method.

holder.imgProductParts.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View v) {
                onPositionChangeListener.onItemChanged(position);
            }

            @Override
            public void onViewDetachedFromWindow(View v) { }
        });
Tippu Fisal Sheriff
  • 2,177
  • 11
  • 19
0

Kotlin Extension Way

Will return the recycler view current item position

Extension Function ==>

fun RecyclerView.onScrollDoneGetPosition(onScrollUpdate: (Int) -> Unit) {
    this.addOnScrollListener(object :
        RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
        }

        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            when (newState) {
                AbsListView.OnScrollListener.SCROLL_STATE_FLING -> {
                }
                AbsListView.OnScrollListener.SCROLL_STATE_IDLE -> {
                    print("When User Done it's Scroll")
                    val currentPosition =
                        (this@onScrollDoneGetPosition.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
                    onScrollUpdate.invoke(currentPosition)
                }
                AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL -> {
                }
            }
        }
    })
}

Usage ==>

yourRecyclerView.onScrollDoneGetPosition() { pos ->
 populateData(pos)
}

Additional ==>

recycler view only one item per swipe restriction

 val snap = PagerSnapHelper()
 snap.attachToRecyclerView(yourRecyclerView)

## Happy codding ##

Ammar
  • 765
  • 1
  • 8
  • 18