How can I identify that RecyclerView's last item is visible on screen?
Asked Answered
M

4

20

I have one RecyclerView and I added list of data into the RecyclerView. I wanted to add more data in list, when last RecyclerView item is visible on screen. After that I want to make a web service call and update the RecyclerView data. How can I achieve this?

Any suggestions?

Meteor answered 26/1, 2016 at 9:27 Comment(6)
use android.support.v7.widget.RecyclerView.OnScrollListenerNegative
i know about scroll listener but my problem is when user delete in between items then how do i know my last list item is get visible so i can update list or make a web service call ?Meteor
so use onBindViewHolder thenNegative
if position == getItemCount() - 1 that it means the last item is visibleNegative
In onBindViewHolder() check if the position is the last position. If it is so, check this if (View.getVisibility() == View.VISIBLE)Dorso
yes it worked for meKarren
B
19

One option would involve editing your LayoutManager. The idea here is to find the position of the last visible item. If that position is equal to the last item of your dataset, then you should trigger a reload.

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {

        final int result = super.scrollVerticallyBy(dy, recycler, state);

        if (findLastVisibleItemPosition() == mData.length - 1) {
           loadMoreData();
        } 

        return result;

    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        super.onLayoutChildren(recycler, state);

        if (findLastVisibleItemPosition() == mData.length - 1) {
           loadMoreData();
        } 
    }

Alternatively, you could do this via your adapter's onBindViewHolder method, although this is admittedly a bit of a "hack":

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == mData.length - 1) {
            // load more data here.
        }

        /// binding logic
    }

3rd option would be to add an OnScrollListener to the RecyclerView. @velval's answer on this page explains this well.

Regardless which option you go for, you should also include code to prevent the data load logic from triggering too many times (e.g., before the previous request to fetch more data completes and returns new data).

Brevier answered 26/1, 2016 at 9:42 Comment(12)
I remember recyclerview will have couple of more viewholders binded which will be off the screen. So, I think checking for the last item may not helpDorso
it will bind it when the view is just about to become visible, so I believe it should be good enough for you. I will add another option thoBrevier
Yes, it will bind when it is ABOUT to become visible. So, the last one that binded need not to have been visible already. So, we still need condition to check visibility. He may also try findLastVisibleItemPosition() in LinearLayoutManager.Dorso
accepatable answer but when i am scrolling it will called two times for webservice (first time on scroll and second last visible item in adapter )Meteor
Hey @GilMoshayof please explain how can i use this your second option with LinearLayoutManager?Meteor
Well, like I said, you will need to implement safeguards to prevent sending a request twice. Perhaps you could hold a token indicating that a request has been sent when the dataset was at the current size, and if an attempt to load more items is made and the dataset is still in the same size, the attempt is ignored.Brevier
@GilMoshayof can you help me with this one link to my questionFeeney
Extremely incorrect answer. onBindViewHolder() function is no place to add/load data. Please do not use this technique.Ecstatic
@BugsHappen, could you please elaborate on this comment? I don't see the harm of queuing a server request to fetch more data once the final item in the data-source is bound. Of course, additional options were mentioned in this answer. Does your comment imply that these options are extremely incorrect as well?Brevier
First method is a (excuse my language) disaster, because it is not the job of onBindViewHolder to look for more data. It should only bind data to the ViewHolder. The second method is some what better but, extending LayoutManager is still a greater job to do for such minor task. Why not simply use RecyclerView.addOnScrollListener and see where the scrolling has reached, like in the velval answer below.Ecstatic
@BugsHappen no worries re-the language. "Disaster" isn't really a slur. I'm afraid I have to disagree with your position though. To me the scroll answer seems a bit "hacky", and I still feel that binding the last item is the most natural way to do this. I suppose we'll just agree to disagree. I would, however, try to urge you to be a little more open minded. Only a Sith deals in absolutes ;)Brevier
onBindViewHolder hack is definitely too much and not a good way in my opinion. I would recommend using a ScrollListener for this task. The question is clearly about paging or I understood wrong. !Compensable
Q
11

If someone stumble across this post this is a quick and simple tutorial on how to do it:

All you need to do is:

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

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        int visibleItemCount        = lm.getChildCount();
        int totalItemCount          = lm.getItemCount();
        int firstVisibleItemPosition= lm.findFirstVisibleItemPosition();

        // Load more if we have reach the end to the recyclerView
        if ( (visibleItemCount + firstVisibleItemPosition) >= totalItemCount && firstVisibleItemPosition >= 0) {
            loadMoreItems();
        }
    }
});

Then your loadMoreItems() should look something like this:

private void loadMoreItems() {
    // init offset=0 the frist time and increase the offset + the PAGE_SIZE when loading more items
    queryOffset = queryOffset + PAGE_SIZE;
    // HERE YOU LOAD the next batch of items
    List<Items> newItems = loadItems(queryOffset, PAGE_SIZE);

    if (newItems.size() > 0) {
        items.addAll(newItems);
        adapter.notifyDataSetChanged();
    }
}
Quartern answered 23/5, 2017 at 14:28 Comment(1)
What about reverse?Cockatiel
D
0

Seen many of the above answers but my answer is different one and it will work in your cases also. My approach is based on scroll state of recylerview. Maintain below variable "check" and this should update only once when api responds. Put below code in your api response. If you want to handle last item only on every call of api.

final boolean[] check = {true};
    recyclerView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            if (!recyclerView.canScrollVertically(1)) {
                // last item of recylerview reached.
                if (check[0]) {
                    //your code for last reached item
                    scroll_handler.setVisibility(View.GONE);
                }
            } else {
                scroll_handler.setVisibility(View.VISIBLE);
                check[0] = false;
            }
        }
    });

If you want to handle your last item every time then do it as below

recyclerView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
    @Override
    public void onScrollChanged() {
        if (!recyclerView.canScrollVertically(1)) 
            // Bottom of recyler view.
            arrow_img.setRotation(180);
        }
    }
});
Dd answered 19/12, 2019 at 7:6 Comment(0)
C
0

See also Android - Detect when the last item in a RecyclerView is visible.

private fun isLastItemVisible(): Boolean {
    val layoutManager = recycler_view.layoutManager
    val position = layoutManager.findLastCompletelyVisibleItemPosition()
    return position >= adapter.itemCount - 1
}
Culley answered 7/8, 2020 at 13:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.