4

How can we implement infinite scrolling in ViewPager2

As there is no OnPageChangeListener() in this pager. But there is setPageTransformer() Listener which get callback whenever we change page.

So any solution for viewpager2 problem

Note : I'm using recyclerview adapter for viewpager2

Ajeett
  • 814
  • 2
  • 7
  • 18
nikhil lohar
  • 139
  • 3
  • 14

5 Answers5

9

For infinite scrolling, you don't have to customise the view pager. The trick in the viewPager adapter. You can return Int.MAX value in getCount override method. The in the instantiateItem override function you can use item_position % item_size to get the actual item position. Code example is given below.

class AdsSliderAdapter(
private val bannerImageList: MutableList<SliderImagesItem>,
private val callback: SliderItemClickListener) :PagerAdapter() {

private var mContext: Context? = null
override fun instantiateItem(container: ViewGroup, position: Int): Any {
    mContext = container.context

    val view = LayoutInflater.from(container.context)
        .inflate(R.layout.item_ad_slider, container, false)

    val adImage: AppCompatImageView = view.ivAd
    mContext?.let {
        GlideApp.with(it).load(bannerImageList[position % bannerImageList.size].imageUrl)
            .into(adImage)
    }
    val viewPager = container as ViewPager
    viewPager.addView(view, 0)

    view.cardView.onClick { callback.onSliderImageClick(bannerImageList[position % bannerImageList.size]) }
    return view
}

override fun isViewFromObject(view: View, `object`: Any): Boolean {
    return view === `object`
}

override fun getCount(): Int {
    return if (bannerImageList.size > 0) {
        Int.MAX_VALUE
    } else {
        0
    }
}

override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
    val viewPager = container as ViewPager
    val view = `object` as View
    viewPager.removeView(view)
}

interface SliderItemClickListener {
    fun onSliderImageClick(item: SliderImagesItem)
}

}

Rafiqul Hasan
  • 3,324
  • 4
  • 20
  • 28
5

Step 1 : Create EndlessScrollAdapter class

class EndlessScrollAdapter internal constructor(
    fm: FragmentManager,
    lifeCycle: Lifecycle
) : FragmentStateAdapter(fm, lifeCycle) {

    private val items = mutableListOf<Model>()
    val firstElementPosition = Int.MAX_VALUE / 2

    fun updateList(list: List<Model>) {
        items.apply {
            clear()
            addAll(list)
        }
        notifyDataSetChanged()
    }

    override fun getItemCount(): Int = if (items.isNotEmpty()) Int.MAX_VALUE else 0

    override fun createFragment(position: Int): Fragment = ViewPagerFragment(
        items[position.rem(items.size)])
}

Step : 2 Call from Activity or Fragment

viewPager2.adapter = endlessScrollAdapter
endlessScrollAdapter.apply {
  updateList(someModelList)
  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 length of ViewPager2 is Int.MAX_VALUE, the start position is Int.MAX_VALUE/2 so user can scroll forward and backwards.

Kalpesh Rupani
  • 991
  • 4
  • 12
  • Yes but i wanted for recylerview. and I got the answer thanks for the response – nikhil lohar Jan 29 '20 at 04:52
  • 1
    @nikhillohar Glad to help! – Kalpesh Rupani Jan 29 '20 at 04:59
  • Thank you very much @KalpeshRupani !!, this works, but is giving me an untraceable memory leak that's pointing to a library line... (FragmentViewHolder.itemView). I am in Java using the fragmentClass.newInstance() method of creating Fragments... Maybe that's what I'm doing wrong. Any advice?? – Delark Apr 24 '21 at 00:43
3

Since you mentioned you were using recyclerview adapter for viewpager2 have you tried writing it as you would while implementing infinite scroll in a recyclerview.
Basically you need to return Integer.MAX_VALUE in your adapter's getItemCount() method and whenever in onBindViewHolder you need actual item position always use position % yourList.size() . I've also added the size > 0 check in order to avoid crashes when list is empty and therefore this would otherwise throw a division by zero error.

Below is a sample adapter, you can use it as reference for your adapter and check if it works well for you. Hope this helps.

public class SampleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

private List<Sample> mSampleList;

public SampleAdapter(List<Sample> mSampleList) {
    this.mSampleList = mSampleList;
}

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View viewCommonItems = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.demo_view, parent, false);
    ItemViewHolder commonHolders = new ItemViewHolder(viewCommonItems);
    return commonHolders;

}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    ItemViewHolder viewHolder = (ItemViewHolder) holder;
    if(mSampleList.size()>0) {
        Sample sample = mSampleList.get(position % mSampleList.size());
        Glide.with(viewHolder.itemView.getContext()).load(sample.getImage())
                .into(viewHolder.ivImage);
    }
}

@Override
public int getItemCount() {
    return Integer.MAX_VALUE;
}

public class ItemViewHolder extends RecyclerView.ViewHolder {
    @BindView(R.id.iv_img)
    ImageView ivImage;
    public ItemViewHolder(@NonNull View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }
}

}

ljk
  • 1,496
  • 1
  • 11
  • 14
0

You can use the code below

public class YourPagerAdapter extends FragmentStatePagerAdapter
{
    public static int COUNT_OF_LOOP = 1000;
    private ArrayList<Product> mProducts;


    public YourPagerAdapter(FragmentManager manager, ArrayList<Product> products)
    {
        super(manager);
        mProducts = products;
    }


    @Override
    public Fragment getItem(int position)
    {
        if (mProducts != null && mProducts.size() > 0)
        {
            position = position % mProducts.size(); 
            return MyFragment.newInstance(mProducts.get(position));
        }
        else
        {
            return MyFragment.newInstance(null);
        }
    }


    @Override
    public int getCount()
    {
        if (mProducts != null && mProducts.size() > 0)
        {
            return mProducts.size()*COUNT_OF_LOOP;
        }
        else
        {
            return 1;
        }
    }
} 

mAdapter = new YourPagerAdapter(getSupportFragmentManager(), mProducts);
mViewPager.setAdapter(mAdapter);
mViewPager.setCurrentItem(mViewPager.getChildCount() * YourPagerAdapter. COUNT_OF_LOOP / 2, false);
Ajeett
  • 814
  • 2
  • 7
  • 18
  • 5
    You realize that this is not infinite because if there is just a single product then it will only scroll a thousand times. – Rafsanjani Jan 16 '20 at 11:34
0

Try below code

@Override
public void onPageScrollStateChanged (int state) {
    if (state == ViewPager.SCROLL_STATE_IDLE) {
        int curr = viewPager.getCurrentItem();
        int lastReal = viewPager.getAdapter().getCount() - 2;
        if (curr == 0) {
            viewPager.setCurrentItem(lastReal, false);
        } else if (curr > lastReal) {
            viewPager.setCurrentItem(1, false);
        }
    }
}

I hope this can help You!

Thank You.

Hardik Talaviya
  • 1,396
  • 5
  • 18