I have a FragmentStateAdapter that is attached to new ViewPager2. In the previous ViewPager implementation there were number of ways to implement endless/infinite scroll. How to do so in ViewPager2?
Thanks in advance.
I have a FragmentStateAdapter that is attached to new ViewPager2. In the previous ViewPager implementation there were number of ways to implement endless/infinite scroll. How to do so in ViewPager2?
Thanks in advance.
 
    
    To achieve endless swipe using viewpager 2 we can do the following:-
originalList.add(0, lastElement) // last element at the first position
originalList.add(firstElement)   // first element at the last
viewpager.setCurrentItem(1, false)
val recyclerView = viewpager.getChildAt(0) as RecyclerView
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val itemCount = viewpager.adapter?.itemCount ?: 0
// attach scroll listener
recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
     override fun onScrolled(
        recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        val firstItemVisible 
            = layoutManager.findFirstVisibleItemPosition()
        val lastItemVisible 
            = layoutManager.findLastVisibleItemPosition()
        if (firstItemVisible == (itemCount - 1) && dx > 0) {
            recyclerView.scrollToPosition(1)
        } else if (lastItemVisible == 0 && dx < 0) {
            recyclerView.scrollToPosition(itemCount - 2)
        }
    }
})
After these three steps we can scroll / swipe infinitely through the viewpager2.
Credits: Viewpager 2 infinite swipe
 
    
     
    
    This is what I came up with:
class EndlessScrollAdapter internal constructor(
    fm: FragmentManager,
    lifeCycle: Lifecycle
) : FragmentStateAdapter(fm, lifeCycle) {
    private val items = mutableListOf<YourModel>()
    val firstElementPosition = Int.MAX_VALUE / 2
    fun updateList(list: List<YourModel>) {
        items.apply {
            clear()
            addAll(list)
        }
        notifyDataSetChanged()
    }
    override fun getItemCount(): Int = if (items.isNotEmpty()) Int.MAX_VALUE else 0
    override fun createFragment(position: Int): Fragment = YourPagerFragment(
        items[position.rem(items.size)])
}
In Activity or Fragment we need to call:
viewPager2.adapter = endlessScrollAdapter
endlessScrollAdapter.apply {
  updateList(yourModelList)
  viewPager2.setCurrentItem(this.firstElementPosition, false)
}
Literally it's not endless but from the user perspective it is, as he will never reach to the edge. The lenght of ViewPager2 is Int.MAX_VALUE, the start position is Int.MAX_VALUE/2 so user can scroll foward and backwards.
 
    
    I am quite new to android and I passed the whole day reading all posts I could find about this problem. I also have a FragmentStateAdapter with 3 Fragments attached to a viewPager2.
My solution was to override the OnPageChangeCallback event. Here, I saved in 2 local variables the state and the currentPosition; then, in the onPageScrolled, I added a control to understand:
If all these conditions were true and I was on the first page, then I would set the next page to be the last and vice versa.
Here is my code. I used 0 and 2 because I have 3 Fragments.
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback()
{
    private int myState;
    private int currentPosition;
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
    {
        if (myState == ViewPager2.SCROLL_STATE_DRAGGING && currentPosition == position && currentPosition == 0)
            viewPager2.setCurrentItem(2);
        else if (myState == ViewPager2.SCROLL_STATE_DRAGGING && currentPosition == position && currentPosition == 2)
            viewPager2.setCurrentItem(0);
        super.onPageScrolled(position, positionOffset, positionOffsetPixels);
    }
    @Override
    public void onPageSelected(int position)
    {
        currentPosition = position;
        super.onPageSelected(position);
    }
    @Override
    public void onPageScrollStateChanged(int state)
    {
        myState = state;
        super.onPageScrollStateChanged(state);
    }
});
 
    
    I wrote a program that prepares three pages and when you move from the center page, the contents of the pages are shifted and go back to the center page. I am glad if it is useful to you.
(All I have is 4 months of programming experience. So this code is should be used only as a reference)
In your Fragment or Activity
・・・
private ViewModel viewModel;
private ViewPager2 pager2;
private List<Integer> numbers;
private int currentPosition;
public View onCreateView(@NonNull LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {
    ・・・
    pager2.setCurrentItem(1, false);
    ・・・
}
private ViewPager2.OnPageChangeCallback pageChangeCallback = new ViewPager2.OnPageChangeCallback() {
    @Override
    public void onPageSelected(final int position) {
        super.onPageSelected(position);
        currentPosition = position;
    }
    @Override
    public void onPageScrollStateChanged(final int state) {
        super.onPageScrollStateChanged(state);
        //If you moved from center page, repack the contents of the pages and go back to center page.
        if(state == pager2.SCROLL_STATE_IDLE && currentPosition != 1){
            items = viewModel.shiftNumbers(numbers, 2-currentPosition, (currentPosition-1)*3);
            adapter.notifyDataSetChanged();
            pager2.setCurrentItem(1, false);
        }
    }
};
@Override
public void onDestroyView() {
    super.onDestroyView();
    binding = null;
}
In your ViewModel
private List<Integer> numbers;
public ViewModel() {
    numbers = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
        numbers.add(i, i-1);
    }
}
public List<Integer> getNumbers(){
return numbers;
}
public List<Integer> shiftNumbers (List<Integer> numbers, int pickPosition, int difference){
   //When moving to page 0 of [0,1,2], the contents of the page 2 are replaced with the contents that should be on page -1. Moving to page 2 is handled in the same way.
    int number = numbers.get(pickPosition);
    number = number+difference;
    //Then repack.
    numbers.remove(pickPosition);
    numbers.add(2-pickPosition,number);
    return numbers;
}
Adapter and ViewHolder are ok to be the default. This can be applied when you want to display the previous/next page as well. But using PageTransformer will be a little bit difficult.
 
    
    you should return first or last position of view_pager when scrolling finished.
see this issue - Changing ViewPager to enable infinite page scrolling
Define a class extending ViewPager2.OnPageChangeCallback :
class ViewPager2PageChangeCallback(private val listener: (Int) -> Unit) :
        ViewPager2.OnPageChangeCallback() {
    override fun onPageSelected(position: Int) {
        super.onPageSelected(position)
        when (position) {
            0 -> listener.invoke(5). // 0th element is a fake element in your list. When 0th element is opened(from 1 to 0) automatically open last element(5th index)
            6 -> listener.invoke(1). // 6th element is a fake element in your list. When 6th element is opened(from 5 to 6) automatically open first element(1th index)
        }
    }
}
Then create an instance from it in your activity.
onCreate(){
    viewPager2PageChangeCallback = ViewPager2PageChangeCallback {
        viewPager2.post {
            viewPager2.setCurrentItem(it, false)
        }
    }
    viewPager2.registerOnPageChangeCallback(viewPager2PageChangeCallback)
}
Unregister from it when activity is destroyed.
override fun onDestroy() {
    super.onDestroy()
    viewPager2.unregisterOnPageChangeCallback(viewPager2PageChangeCallback)
}
