3

I have a RecyclerView as a Calendar, and each view is a day.

(occupying the full width and height of the screen)

     .----------------.   .----------------.   .----------------.   .----------------.   .----------------.   .----------------.   .----------------.
    | .--------------. | | .--------------. | | .--------------. | | .--------------. | | .--------------. | | .--------------. | | .--------------. |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | |     (1)      | | | |     (2)      | | | |     (3)      | | | |     (4)      | | | |     (5)      | | | |    (...)     | | | |     (n)      | |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | '--------------' | | '--------------' | | '--------------' | | '--------------' | | '--------------' | | '--------------' | | '--------------' |
     '----------------'   '----------------'   '----------------'   '----------------'   '----------------'   '----------------'   '----------------'

The problem:

The problem is, when a user is trying to scroll to a date that is very far away in the future, i.e move ahead 40 days, onBindViewHolder runs for all the 39 children in-between, which wastes resources, as the scroll is so fast, no-one even knows what each day contains during that scroll.

As a result the last day takes some time before it shows the content, because the main thread is busy drawing all the in-between days that will never be visible unless the user scrolls manually to them anyway.

What's needed:

How can I prevent the RecyclerView from going crazy loading all those in-between days, when all I need at that point is just the day the user ends up to.

     .----------------.   .----------------.   .----------------.   .----------------.   .----------------.   .----------------.   .----------------.
    | .--------------. | | .--------------. | | .--------------. | | .--------------. | | .--------------. | | .--------------. | | .--------------. |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | |              | | | |              | | | |              | | | |              | | | |              | | | |              | | | |              | |
    | |     (1)      | | | |     (2)      | | | |     (3)      | | | |     (4)      | | | |     (5)      | | | |    (...)     | | | |     (n)      | |
    | |    LOAD      | | | |     DONT     | | | |    DONT      | | | |    DONT      | | | |     DONT     | | | |     DONT     | | | |     LOAD     | |
    | '--------------' | | '--------------' | | '--------------' | | '--------------' | | '--------------' | | '--------------' | | '--------------' |
     '----------------'   '----------------'   '----------------'   '----------------'   '----------------'   '----------------'   '----------------'

(Please don't tell me to use scrollToPosition instead of smoothScrollToPosition since removing the animation is not an option)

Thank you

SudoPlz
  • 20,996
  • 12
  • 82
  • 123
  • You cant prevent it from doing one of its basic functions, views are recycled so once they gets created initially there should be very little overhead unless you are trying to do a lot of work in your onBind – tyczj Aug 02 '18 at 20:26

2 Answers2

2

You can modify your Adapter class to check state of smooth scroll:

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    if(recyclerView.getLayoutManager().isSmoothScrolling()){
        // make viewholder into a "blank" here
        return;
    }
    // regular binding
}

Note that it's a little crude - should make the ViewHolder blank during scroll, otherwise it will show invalid days.

This is as elegant (and simple) as it gets, since SmoothScroller is actually really primitive - it just keeps scrolling until target view is found, so there's no callback to know when viewport with target position is already being laid out.

You'll need to revalidate the visible holders when you bind your scroll "target".

More perfect solution can only would work if your ViewHolders have uniform size that do not change, and you provide smoothScroll target to the adapter:

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    if(recyclerView.getLayoutManager().isSmoothScrolling()){
        // scrollTarget is current smoothScroll target position
        int diff = Math.abs(position - scrollTarget);
        // viewHolderHeight is taken/cached from any viewHolder that was laid out previously
        if(diff * viewHolderHeight > recyclerView.getHeight()){
            // make viewholder into a "blank" here
            return;
        }
        // still smoothscrolling, but viewholder will be visible when scroll ends
    }
    // regular binding
}
Pawel
  • 15,548
  • 3
  • 36
  • 36
  • That sounds cool, but that will prevent `onBindViewHolder` from doing work when the user smooth scrolls to the next day or second next day. We do want the `onBindViewHolder` to run then. :/ – SudoPlz Aug 02 '18 at 20:50
  • @SudoPlz as far as I'm aware layoutmanager returns `isSmoothScrolling` only when target view is not within viewport - as soon as it shows on the screen smooth scroll ends. – Pawel Aug 02 '18 at 20:53
  • Still I can't risk to miss the one `onBindViewHolder` event that I need. If the timing is not absolutely perfect the user will see a blank screen on the final day as well. – SudoPlz Aug 02 '18 at 21:42
  • @SudoPlz added to my answer a bit. And there's no "timing" to be afraid of since layoutmanager and adapter both work on the same UI thread. – Pawel Aug 02 '18 at 22:54
  • Hi thanks for the update, is there a way to receive the scroll target from the layout manager or the recycler view itself ? – SudoPlz Aug 03 '18 at 07:58
  • @SudoPlz not unless you write a subclass for either, there's no direct getter in default implementation. – Pawel Aug 03 '18 at 08:13
0

After spending almost 2 days debugging this, I figured some sort of a work-around.

The problem ended up being the scrollBy method, which I was using for more control over where I scrolled to.

Even-though the scroll did NOT pass through any of the in-between children, onBindViewHolder was invoked.. This is super weird and it makes no sense at all to me, (is it a bug with RecyclerView?)

I ended up using scrollToPosition which only invokes onBindViewHolder for the child I end up to - and therefore resolves all the issues I've been getting.

Yes I sacrifice scroll precision but at least I only bind what I need to - AT LAST!

SudoPlz
  • 20,996
  • 12
  • 82
  • 123