Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ViewPager as a circular queue / wrapping

I am using a ViewPager with the FragmentStatePagerAdapter to allow navigation between some fragments.

Let's say I have three fragments: A, B and C. The ViewPager shows Fragment A initially, and allows you to navigate to Fragment B by swiping from right-to-left, and then to Fragment C by swiping again. This allows the following navigation paths: A <--> B <--> C.

What I would like is to be able to swipe from left-to-right on Fragment A and have the ViewPager show Fragment C, i.e. for it to behave as a circular queue and allow ... --> C <--> A <--> B <--> C <--> A <-- ...

I do not want the Fragments duplicated in other positions (i.e. ending up with more than three instances).

Is this wrapping functionality possible with a ViewPager?

like image 318
antonyt Avatar asked Sep 25 '11 14:09

antonyt


3 Answers

I've implemented a ViewPager/PagerAdapter that can allow for pseudo-infinite paging behaviour. It works by specifying a very large number as the actual count, but maps them to the actual range of the dataset/pageset. It offsets the beginning by a large number also so that you can immediately scroll to the left from the 'first' page.

It doesn't work so well once you get to 1,000,000th page (you will see graphical glitches when scrolling), but this is typically not a real-world use-case. I could fix this by resetting the count to a lower number once in a while, but I will leave it how it is for now.

The InfinitePagerAdapter wraps an existing ViewPager, and so the usage is quite transparent. The InfiniteViewPager does does a bit of work to make sure you can potentially scroll to the left and right many times.

https://github.com/antonyt/InfiniteViewPager

like image 159
antonyt Avatar answered Sep 23 '22 03:09

antonyt


This is a solution without fake pages and works like a charm:

public class CircularViewPagerHandler implements ViewPager.OnPageChangeListener {
    private ViewPager   mViewPager;
    private int         mCurrentPosition;
    private int         mScrollState;

    public CircularViewPagerHandler(final ViewPager viewPager) {
        mViewPager = viewPager;
    }

    @Override
    public void onPageSelected(final int position) {
        mCurrentPosition = position;
    }

    @Override
    public void onPageScrollStateChanged(final int state) {
        handleScrollState(state);
        mScrollState = state;
    }

    private void handleScrollState(final int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            setNextItemIfNeeded();
        }
    }

    private void setNextItemIfNeeded() {
        if (!isScrollStateSettling()) {
            handleSetNextItem();
        }
    }

    private boolean isScrollStateSettling() {
        return mScrollState == ViewPager.SCROLL_STATE_SETTLING;
    }

    private void handleSetNextItem() {
        final int lastPosition = mViewPager.getAdapter().getCount() - 1;
        if(mCurrentPosition == 0) {
            mViewPager.setCurrentItem(lastPosition, false);
        } else if(mCurrentPosition == lastPosition) {
            mViewPager.setCurrentItem(0, false);
        }
    }

    @Override
    public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
    }
}

You just have to set it to your ViewPager as onPageChangeListener and that's it: ( ** deprecated now ** check the edit notes)

viewPager.setOnPageChangeListener(new CircularViewPagerHandler(viewPager));

To avoid having this blue shine at the "end" of your ViewPager you should apply this line to your xml where the ViewPager is placed:

android:overScrollMode="never"

We have improved the solution above and created a little library on github. Feel free to check it out :)

Edit:: As per the @Bhavana comment , Just use addOnPageChangeListener instead of setOnPageChangeListener as later is deprecated.

 viewPager.addOnPageChangeListener(new CircularViewPagerHandler(viewPager));
like image 25
King of Masses Avatar answered Sep 22 '22 03:09

King of Masses


I've been trying all the suggestions, solutions, libraries, etc but they're not pure circular and most of the time don't have support for only 3 pages.

So I implemented a circular ViewPager example using the new ViewPager2, the new ViewPager uses a RecyclerView and ViewHolders to handle the views recycling and works as expected!

TLDR: GITHUB

In this example, will be building a single activity app with ViewPager2 and a FragmentPagerAdapter supporting circular navigation between 3 pages or more.

I'm using an alpha version of the library androidx.viewpager2:viewpager2, but the version 1.0.0-alpha06 is the last one planned before google freezing the API and moving to beta.

enter image description here

1. Add the ViewPager2 library to the dependencies in your build.gradle

dependencies {
    implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha06'
}

2. Add the ViewPager2 view to your project:

<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/vwpHome"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3. Create the FragmentStateAdapter adapter:

getItemCount() needs to returns a huuuuge number. (2147483647)

getCenterPage() returns the central page based on the huuuuge number. This method is used to get the position of the initial page to set in the ViewPager2, in this case the user needs to swipe ˜1073741823 time to reach the end of the ViewPager2.

class CircularPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) {

    override fun getItemCount() = Integer.MAX_VALUE

    /**
     * Create the fragment based on the position
     */
    override fun createFragment(position: Int) = HomePagerScreens.values()[position % HomePagerScreens.values().size].fragment.java.newInstance()

    /**
     * Returns the same id for the same Fragment.
     */
    override fun getItemId(position: Int): Long = (position % HomePagerScreens.values().size).toLong()

    fun getCenterPage(position: Int = 0) = Integer.MAX_VALUE / 2 + position
}

HomeScreens is a ENUM with the page info.

enum class HomePagerScreens(@StringRes val title: Int,
                            val fragment: KClass<out Fragment>) {

    HOME_1(R.string.home_1, FragmentHome::class),
    HOME_2(R.string.home_2, FragmentHome::class),
    HOME_3(R.string.home_3, FragmentHome::class)
}

4. Set the adapter to the ViewPager

val circularAdapter = CircularPagerAdapter(supportFragmentManager, lifecycle)
vwpHome.apply {
    adapter = circularAdapter
    setCurrentItem(circularAdapter.getCenterPage(), false)
}
like image 33
extmkv Avatar answered Sep 24 '22 03:09

extmkv