289

I need to know which elements are currently displayed in my RecyclerView. There is no equivalent to the OnScrollListener.onScroll(...) method on ListViews. I tried to work with View.getGlobalVisibleRect(...), but that hack is too ugly and does not always work too.

Someone any ideas?

rekire
  • 45,039
  • 29
  • 149
  • 249

10 Answers10

621

First / last visible child depends on the LayoutManager. If you are using LinearLayoutManager or GridLayoutManager, you can use

int findFirstVisibleItemPosition();
int findFirstCompletelyVisibleItemPosition();
int findLastVisibleItemPosition();
int findLastCompletelyVisibleItemPosition();

For example:

GridLayoutManager layoutManager = ((GridLayoutManager)mRecyclerView.getLayoutManager());
int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();

For LinearLayoutManager, first/last depends on the adapter ordering. Don't query children from RecyclerView; LayoutManager may prefer to layout more items than visible for caching.

Vasily Kabunov
  • 5,179
  • 12
  • 41
  • 46
yigit
  • 34,323
  • 13
  • 70
  • 58
  • I totally missed that. – rekire Jul 31 '14 at 07:54
  • 11
    Is there any possibility to access this methods from RecyclerView.Adapter without passing reference to LayoutManager? – Yorgi Dec 10 '14 at 00:09
  • 3
    Like @Yorgi - inside the adapter seems useful. I wonder if there is some pattern you can develop in using onBindViewHolder to track which ones are actually on-screen while a user scrolls. – RoundSparrow hilltx Apr 22 '15 at 15:21
  • @RoundSparrowhilltx you will more likely want to do it in onView(At/De)tachedToWindow - RecyclerView not only recycles but caches items which means onBindViewHolder is not called for each view that's about to go on screen – Alexandre G Jul 07 '15 at 04:36
  • Why can't I use mLinearLayoutManager directly instead of having to use method mRecyclerView.getLayoutManager? – Juan José Melero Gómez Jul 15 '15 at 20:47
  • 9
    `getItemCount` can return 1 and `findFirstVisibleItemPosition` -1 on the same call. – mbmc Aug 24 '15 at 20:49
  • @tibo, that might be ok if you call it after changing the adapter contents but before the new layout is calculated. That means there is data in the adapter but we have not laid out the views yet. – yigit Aug 25 '15 at 00:36
  • 4
    what can you suggest about StaggeredLayoutManager? – hornet2319 Sep 10 '15 at 07:49
  • What about StaggeredLayoutManager? – Pratik Butani Sep 23 '15 at 12:44
  • How to get an actual view? `layoutManager.getChildAt(int index)` more often returns `null` instead of a `View`! – dVaffection Nov 24 '15 at 05:58
  • quick one, How do I then Set that value? I want to recover after a screen rotation, I am saving index in bundle and want to recover in onCreateView? – Zapnologica Feb 17 '16 at 15:55
  • For grid layouts (staggered and otherwise) you need to use the variant that will return multiple visible cells. The reason for this is because you have multiple columns you may have multiple visible cells per column. http://developer.android.com/reference/android/support/v7/widget/StaggeredGridLayoutManager.html#findFirstCompletelyVisibleItemPositions(int[]) – BK- Mar 09 '16 at 00:00
  • 47
    These methods are so unreliable they are completly useless. Especially after notifyDataSetChanged/itemRemoved/RangeChanged – Kaloyan Roussev Jun 07 '16 at 10:05
  • 3
    findFirstCompletelyVisibleItemPosition(); works for item that completely shown on display.. how about item which longer that screen? – yfsx Aug 25 '16 at 03:07
  • With v17 onwards (leanback) the GridLayoutManager is no longer public. – Rob Nov 22 '17 at 11:48
  • Really helpful!! (y) – Stuti Kasliwal Jan 22 '18 at 12:39
  • 3
    I came here to find which method to use reliably in order to get the position AFTER notify*Changed/Removed etc method calls. Still no answer found. :/ – SudoPlz May 31 '18 at 19:31
  • 2
    findLastVisibleItemPosition return last item of list not last item which is view. For example recyclerview has 23 entry only 5 are visible. so findLastVisibleItemPosition return it return 23 not 5 – Ashish Garg Jan 10 '19 at 09:11
  • I tried to get current visible top item inside onScrollChanged method with SCROLL_STATE_IDLE, but current visible item always returning 0, I have 30 items on recyclerView. Any suggestion please ? – Waseem Jan 23 '19 at 16:46
  • 1
    @AshishGarg I agree with your comment – Waseem Jan 23 '19 at 16:48
  • 1
    @J. K., did you find an alternative to using those? – Jack May 05 '19 at 08:54
  • Where should I use this method? Inside Adapter or onScrolled()/onScrollStateChanged()? – VVB Jun 16 '19 at 01:13
  • I discovered my recycler view was inside a nested scroll view in my XML, which was causing the recycler view to actually fit the parent, and think it was displaying all of it's items. The fix is to obviously kill the nested scroll view with fire! (I inherited an android project in the new job ) – Alan Jul 05 '19 at 08:55
  • @AshishGarg same here. I think my problem is caused by using a recyclerview inside a nestedscrollview – Andrew Chelix Oct 27 '20 at 21:00
  • Why this answer got so many upvotes when there are known bugs as mention in the comments? – Farid Jan 28 '21 at 11:22
19

for those who have a logic to be implemented inside the RecyclerView adapter you can still use @ernesto approach combined with an on scrollListener to get what you want as the RecyclerView is consulted. Inside the adapter you will have something like this:

@Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if(manager instanceof LinearLayoutManager && getItemCount() > 0) {
            LinearLayoutManager llm = (LinearLayoutManager) manager;
            recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                }

                @Override
                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
                        int visiblePosition = llm.findFirstCompletelyVisibleItemPosition();
                        if(visiblePosition > -1) {
                            View v = llm.findViewByPosition(visiblePosition);
                            //do something
                            v.setBackgroundColor(Color.parseColor("#777777"));
                        }
                }
            });
        }
    }
Aleyam
  • 931
  • 1
  • 9
  • 10
  • doesn't `llm` variable need to be final in order that the anonymous class will "know" him? – Eitanos30 Jan 13 '21 at 15:23
  • i'm not really an expert but i think the problem is not that the anonymous class "know" ```llm``` but instead, to make sure when the callbacks are called, the value of ```llm``` is the same as when the anonymous class was instantiated to avoid inconsistency correct me if i'm wrong either way the value of ```llm``` was never changed also i didn't received no warning nor compiler errer when i posted my answer. So let me know if i'm wrong or missing something – Aleyam Jan 13 '21 at 19:19
16

Finally, I found a solution to know if the current item is visible, from the onBindViewHolder event in the adapter.

The key is the method isViewPartiallyVisible from LayoutManager.

In your adapter, you can get the LayoutManager from the RecyclerView, which you get as parameter from the onAttachedToRecyclerView event.

Ernesto Vega
  • 411
  • 6
  • 12
  • But where would you call this method in which the layout manager is not null? – 6rchid Jan 03 '19 at 23:21
  • 1
    You need to wait for the onAttachedToRecyclerView event of the adapter. In that event you receive the RecyclerView as parameter; and then you can get the LayoutManager from the RecyclerVie, and save it in a global var. If the LayoutManager is null, probably could be because it wasn't assigned to the RecyclerView in the Activity/Fragment/Layout; and try to build the adapter after that assignment. – Ernesto Vega Jan 04 '19 at 09:07
  • According to the source code, isViewPartiallyVisible is badly broken. If completelyVisible is true, it will return true if the view is fully visible. However, if false, it just negates the result which returns true if the view is partially visible or not visible. Even the documentation doesn’t make sense. – Molanda Nov 12 '19 at 04:28
  • https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java#9942 – Molanda Nov 12 '19 at 04:32
  • @ErnestoVega but onBindViewholder gets called multiple times for the same viewholder,that means isViewPartiallyVisible will return true multiple times even if the view was viewed once – saket Feb 05 '21 at 19:39
8

You can use recyclerView.getChildAt() to get each visible child, and setting some tag convertview.setTag(index) on these view in adapter code will help you to relate it with adapter data.

Sreejith B Naick
  • 1,195
  • 6
  • 12
  • The problem with getChildAt() is that the order of the elements can be out of 5 elements: 0, 5, 4, 3, 2, 1 (the child count is mostly >= the count of visible items). So I tried to get the order of the element via getGlobalVisibleRect. – rekire Jul 28 '14 at 06:50
  • What's your actual requirement, get visible child(child inside screen bound) in actual order ? – Sreejith B Naick Jul 28 '14 at 06:56
  • Now I want to know which element is in the center, later I'm maybe also interested in the bounds. – rekire Jul 28 '14 at 06:59
  • best way to do this,customize recycler view. On each drawChild(), check whether the drawing view is at center. If it is in center, send a listener to outside. – Sreejith B Naick Jul 28 '14 at 07:05
  • Logic to check whether view is in centre is upto you, 2 views can be middle,you have to decide which one to pick. – Sreejith B Naick Jul 28 '14 at 07:09
  • In my special case I have an odd count, so that's not the matter. Extending a widget which surly will change its methods soon... I don't know if I really want that. – rekire Jul 28 '14 at 07:13
  • You start out saying: "You can use recyclerView.getChildAt() to get each visible child," -- but isn't that a paradox? How do you know which Items are visible... – RoundSparrow hilltx Apr 22 '15 at 15:20
  • 1
    You will only get visible items from `recyclerView.getChildAt()`, thats how generally `RecyclerView` works. `RecyclerView` will try to hold only few child views which are currently visible (ie; within screen bounds, not Visibility as GONE,INVISIBLE) and try to recycle these views when user scrolls. – Sreejith B Naick Apr 23 '15 at 05:49
  • I'd add to use `recyclerView.getChildCount()` in order to get the number of visible children. Then you can do a for-loop `for (int i = 0; i < childCount; i++) { View visibleChild = recyclerView.getChildAt(i); }` – TalkLittle Jan 25 '16 at 23:22
  • 7
    View visibleChild = recyclerView.getChildAt(0); int positionOfChild = recyclerView.getChildAdapterPosition(visibleChild); – Kevin Lee May 04 '16 at 18:05
8

Addendum:

The proposed functions findLast...Position() do not work correctly in a scenario with a collapsing toolbar while the toolbar is expanded.

It seems that the recycler view has a fixed height, and while the toolbar is expanded, the recycler is moved down, partially out of the screen. As a consequence, the results of the proposed functions are too high. Example: The last visible item is told to be #9, but in fact item #7 is the last one that is on screen.

This behavior is also the reason why my view often failed to scroll to the correct position, i.e. scrollToPosition() did not work correctly (I finally collapsed the toolbar programmatically).

Paresh Mangukiya
  • 14,668
  • 7
  • 90
  • 90
  • That is true, but this is how the collapsing toolbar works. I stopped developing Android Apps a year ago, but I can remember that fact. – rekire May 21 '19 at 19:06
7

Following Linear / Grid LayoutManager methods can be used to check which items are visible

int findFirstVisibleItemPosition();
int findLastVisibleItemPosition();
int findFirstCompletelyVisibleItemPosition();
int findLastCompletelyVisibleItemPosition();

and if you want to track is item visible on screen for some threshold then you can refer to the following blog.

https://proandroiddev.com/detecting-list-items-perceived-by-user-8f164dfb1d05

Sumit Jain
  • 760
  • 1
  • 12
  • 18
4

For StaggeredGridLayoutManager do this:

RecyclerView rv = findViewById(...);
StaggeredGridLayoutManager lm = new StaggeredGridLayoutManager(...);
rv.setLayoutManager(lm);

And to get visible item views:

int[] viewsIds = lm.findFirstCompletelyVisibleItemPositions(null);
ViewHolder firstViewHolder = rvPlantios.findViewHolderForLayoutPosition(viewsIds[0]);
View itemView = viewHolder.itemView;

Remember to check if it is empty.

3

Every answer above is correct and I would like to add also a snapshot from my working codes.

recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
           //some code when initially scrollState changes
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            //Some code while the list is scrolling
            LinearLayoutManager lManager = (LinearLayoutManager) recycler.getLayoutManager();
            int firstElementPosition = lManager.findFirstVisibleItemPosition();
            
        }
    });
2

You can find the first and last visible children of the recycle view and check if the view you're looking for is in the range:

var visibleChild: View = rv.getChildAt(0)
val firstChild: Int = rv.getChildAdapterPosition(visibleChild)
visibleChild = rv.getChildAt(rv.childCount - 1)
val lastChild: Int = rv.getChildAdapterPosition(visibleChild)
println("first visible child is: $firstChild")
println("last visible child is: $lastChild")
Oz Shabat
  • 912
  • 10
  • 11
0

I hope below code helps someone define int a above methods. if visibile item position different before item position toast message will show on screen

myRecyclerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);

            LinearLayoutManager manager= (LinearLayoutManager) myRecyclerview.getLayoutManager();
            assert manager != null;
            int visiblePosition = manager.findLastCompletelyVisibleItemPosition();


            if(visiblePosition > -1&&a!=visiblePosition) {
                Toast.makeText(context,String.valueOf(visiblePosition),Toast.LENGTH_SHORT).show();
                //do something
                a=visiblePosition;

            }
        }

        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            //Some code while the list is scrolling


        }
    });
Abdullah
  • 58
  • 4