ChatGPT解决这个技术问题 Extra ChatGPT

Get visible items in RecyclerView

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?


V
Vasily Kabunov

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.


Is there any possibility to access this methods from RecyclerView.Adapter without passing reference to LayoutManager?
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.
getItemCount can return 1 and findFirstVisibleItemPosition -1 on the same call.
what can you suggest about StaggeredLayoutManager?
These methods are so unreliable they are completly useless. Especially after notifyDataSetChanged/itemRemoved/RangeChanged
A
Aleyam

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"));
                        }
                }
            });
        }
    }

doesn't llm variable need to be final in order that the anonymous class will "know" him?
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
E
Ernesto Vega

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.


But where would you call this method in which the layout manager is not null?
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.
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.
@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
S
Sreejith B Naick

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.


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.
What's your actual requirement, get visible child(child inside screen bound) in actual order ?
Now I want to know which element is in the center, later I'm maybe also interested in the bounds.
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.
View visibleChild = recyclerView.getChildAt(0); int positionOfChild = recyclerView.getChildAdapterPosition(visibleChild);
A
Andreas K. aus M.

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 behaviour 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).


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.
I m facing the same issue . Do u have any idea how to get around it other than collapsing the toolbar?
S
Sumit Jain

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


A
Antoine

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) recyclerView.getLayoutManager();
            int firstElementPosition = lManager.findFirstVisibleItemPosition();
            
        }
    });

D
Douglas Nassif Roma Junior

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.


What do you mean "check if it is empty"? How does one check?
viewsIds is an array, and it may be empty.
O
Oz Shabat

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")

This is not Java
But upvoting because the general content you added here did in fact help me out
S
Shubham

For those who are looking for an answer in Kotlin -

    fun getVisibleItem(recyclerView : RecyclerView) {
        recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                 if(newState == RecyclerView.SCROLL_STATE_IDLE) {
                     val index = (recyclerView.layoutManager.findFirstVisibleItemPosition
                     //use this index for any operation you want to perform on the item visible on screen. eg. log(arrayList[index])
                 }
            }
        })
    }

You can explore other methods for getting position as per your use case.

int findFirstCompletelyVisibleItemPosition()
int findLastVisibleItemPosition()
int findLastCompletelyVisibleItemPosition()

A
Abdullah

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


        }
    });