Ali Yucel Akgul
Ali Yucel Akgul

Reputation: 1123

How to add endless/infinite scroll to ViewPager2?

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.

Upvotes: 12

Views: 13767

Answers (6)

Santanu Sur
Santanu Sur

Reputation: 11477

To achieve endless swipe using viewpager 2 we can do the following:-

  1. Add a copy of the last element at the beginning and a copy of the first element at the end.
originalList.add(0, lastElement) // last element at the first position
originalList.add(firstElement)   // first element at the last
  1. After setting the adapter make sure to set the current item as 1.
viewpager.setCurrentItem(1, false)
  1. After setting the adapter extract the recyclerView from the viewpager component and add the following scroll listener to it.
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

Upvotes: 4

YoshiT
YoshiT

Reputation: 11

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.

Upvotes: 1

Da Artagnan
Da Artagnan

Reputation: 1199

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.

Upvotes: 3

Claudiu Razlet
Claudiu Razlet

Reputation: 650

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:

  1. if the swipe event started
  2. if the new position was equal to the current position. Observing the logs I noticed that they were the same only when I tried to scroll from first page to the left or from the last one to the right.
  3. if the page position was first or last

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);
    }
});

Upvotes: 3

oiyio
oiyio

Reputation: 5925

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)
}

Upvotes: 0

HGH806
HGH806

Reputation: 84

you should return first or last position of view_pager when scrolling finished.

see this issue - Changing ViewPager to enable infinite page scrolling

Upvotes: 0

Related Questions