How do I create a circular (endless) RecyclerView?

There is no way of making it infinite, but there is a way to make it look like infinite.

  1. in your adapter override getCount() to return something big like Integer.MAX_VALUE:

    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }
    
  2. in getItem() and getView() modulo divide (%) position by real item number:

    @Override
    public Fragment getItem(int position) {
        int positionInList = position % fragmentList.size();
        return fragmentList.get(positionInList);
    }
    
  3. at the end, set current item to something in the middle (or else, it would be endless only in downward direction).

    // scroll to middle item
    recyclerView.getLayoutManager().scrollToPosition(Integer.MAX_VALUE / 2);
    

The other solutions i found for this problem work well enough, but i think there might be some memory issues returning Integer.MAX_VALUE in getCount() method of recycler view.

To fix this, override getItemCount() method as below :

@Override
public int getItemCount() {
    return itemList == null ? 0 : itemList.size() * 2;
}

Now wherever you are using the position to get the item from the list, use below

position % itemList.size()

Now add scrollListener to your recycler view

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 
        super.onScrolled(recyclerView, dx, dy);
        int firstItemVisible = linearLayoutManager.findFirstVisibleItemPosition();
        if (firstItemVisible != 0 && firstItemVisible % itemList.size() == 0) {
            recyclerView.getLayoutManager().scrollToPosition(0);
        }
    }
});

Finally to start auto scrolling, call the method below

public void autoScroll() {
    final Handler handler = new Handler();
    final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            recyclerView.scrollBy(2, 0);
            handler.postDelayed(this, 0);
        }
    };
    handler.postDelayed(runnable, 0);
}

I have created a LoopingLayoutManager that fixes this issue.

It works without having to modify the adapter, which allows for greater flexibility and reusability.

It comes fully featured with support for:

  • Vertical and Horizontal Orientations
  • LTR and RTL
  • ReverseLayout for both orientations, as well as LTR, and RTL
  • Public functions for finding items and positions
  • Public functions for scrolling programmatically
  • Snap Helper support
  • Accessibility (TalkBack and Voice Access) support

And it is hosted on maven central, which means you just need to add it as a dependency in your build.gradle file:

dependencies {
    implementation 'com.github.beksomega:loopinglayout:0.3.1'
}

and change your LinearLayoutManager to a LoopingLayoutManager.

It has a suite of 132 unit tests that make me confident it's stable, but if you find any bugs please put up an issue on the github!

I hope this helps!


Amended @afanit's solution to prevent the infinite scroll from momentarily halting when scrolling in the reverse direction (due to waiting for the 0th item to become completely visible, which allows the scrollable content to run out before scrollToPosition() is called):

val firstItemPosition = layoutManager.findFirstVisibleItemPosition()
if (firstItemPosition != 1 && firstItemPosition % items.size == 1) {
    layoutManager.scrollToPosition(1)
} else if (firstItemPosition == 0) {
    layoutManager.scrollToPositionWithOffset(items.size, -recyclerView.computeHorizontalScrollOffset())
}

Note the use of computeHorizontalScrollOffset() because my layout manager is horizontal.

Also, I found that the minimum return value from getItemCount() for this solution to work is items.size + 3. Items with position larger than this are never reached.


In addition to solution above. For endless recycler view in both sides you should add something like that:

recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            val firstItemVisible = linearLayoutManager.findFirstVisibleItemPosition()
            if (firstItemVisible != 1 && firstItemVisible % songs.size == 1) {
                linearLayoutManager.scrollToPosition(1)
            }
            val firstCompletelyItemVisible = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
            if (firstCompletelyItemVisible == 0) {
                linearLayoutManager.scrollToPositionWithOffset(songs.size, 0)
            }
        }
    })

And upgrade your getItemCount() method:

@Override
public int getItemCount() {
        return itemList == null ? 0 : itemList.size() * 2 + 1;
}

It is work like unlimited down-scrolling, but in both directions. Glad to help!