RecyclerView: find last visible item immediately after view created
Asked Answered
P

3

14

I'm having a problem with the following methods:

int firstVisibleItemPosition = gridLayoutManager.findFirstVisibleItemPosition();
int lastVisibleItemPosition = gridLayoutManager.findLastVisibleItemPosition();

My goal: save analytic data about what items the user have viewed.

In order to do that, I'm calling this methods in two different scenarios:

  • every time scrolling turned to "idle" state, and checking what are the visible items. in that case I'm getting the expected indexes.

  • when the RecyclerView become "visible" to the user. now that's when the problem starts. I would expect that when the fragment containing the recylcerView is passed onResume() then calling findLastVisibleItemPosition() will return the visible items. but it return -1, in that case. I guess it have something to do with the asynchronous loading of the recyclerView + adapter internal items initialization relative to the fragment/activity lifecycle.

by postpone this code by a few milliseconds - findLastVisibleItemPosition() returns the right indexes. but I don't want to postpone hard coded using handler + delayed runnable, because scheduling delayed runnable is a work-around to what I really want to do: detect when the recycler view finished inflating and drawing to the screen all the views that can feet inside of it..

so my questions are basically:

  • how can I detect when the RecyclerView finished all the initialzation/measuring/inflating and drawing of it child items that feet into screen? (before any user interaction..).

  • is there any reliable good practice way to know exactly what items within' the recycler view is really shown on screen?

Poundfoolish answered 10/7, 2015 at 5:44 Comment(2)
Hi, did you get any reliable answer after this post, @Tal Kanel? I'm kind of facing similar issues.Toomer
@hadez30: my solution is to run the code checking for first visible item from a runnable being post by a handler on the UI thread (not delayed ..). That why it being executed on the end of the UI queue which from my experience happens after the views already attached to the recycler viewPoundfoolish
I
1

What about configuration changes? If the user changes the screen orientation, the visible items will change too. The fact is, visible items are changing constantly according to the state of the RecyclerView, the loaded data, the scroll position and the current screen configuration.

The best thing you can probably do is creating a class which constantly keeps track of the superset of visible items, by making it implement the ItemDecoration interface which is called each time a RecyclerView gets redrawn on screen, and have this component send stats periodically. You would reattach it to the new RecyclerView instance on configuration change (preserving its state).

So for example this component could keep track of the minimum and maximum visible positions. At first it will be -1 for both. Then after the data is loaded and the first items are shown on screen, the ItemDecoration will be called again and first visible item position will now be 0 and last visible item position will now be N. After scroll, the values will change again. You would only keep the min value of FirstVisibleItemPosition and the max value of LastVisibleItemPosition in order to get the superset. After X seconds without changes, or if the user navigates back from the Activity, you would record and send these numbers.

Implant answered 19/2, 2017 at 13:8 Comment(1)
A similar approach would be to track calls to onBindViewHolder to check what items are bound (displayed) and from that determine what items are not displayed.Contextual
N
-1

Use OnChildAttachListener to detect when a the recycler attach a new holder. Use a delay mechanism in order to send the correct analitycs (\not a hardcoded delaybut something like this:

...
public void run(){
  removeCallbacks()
  postDelay(sendAnalytics(correctInformation),25);
}

The 25ms delay more reliable then attaching it ot the onResume arbitrary.

Nimble answered 10/7, 2015 at 9:6 Comment(0)
L
-1

As for answering your first question. The best solution for this problem that I found is to set layout change listener on a recycler view:

recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        recyclerView.removeOnLayoutChangeListener(this); //remove layout change listener, you want need it anymore
        //do your work here
    }
});

As for second question. You can getChildCount() & getChildAt(int) to get info about currently attached child view to recyclerView, but it doesn't mean that they are visible on screen (they can be a little off screen). To check if they are visible see Android: how to check if a View inside of ScrollView is visible?

Lawful answered 10/7, 2015 at 9:18 Comment(1)
onLayoutChange() is invoked when the view itself changed it dimentions, not when his nested childs did. so it will never work with recyclerView that have always pre-defined height and widthPoundfoolish

© 2022 - 2024 — McMap. All rights reserved.