10

I have a ViewPager with some views. I'd like to go to the first one after right swiping on the last one.

I tried

@Override
public Fragment getItem(int arg0) {
    int i = arg0 % fragmentList.size();
    return fragmentList.get(i);
}

@Override
public int getCount() {
    return fragmentList.size()+1;
}

But I got an error

E/AndroidRuntime(22912): java.lang.IllegalStateException: Fragment already added: RubricFragment{4136cd80 #1 id=0x7f06000c android:switcher:2131099660:0}
Mike G
  • 4,022
  • 9
  • 41
  • 63
J3n
  • 276
  • 1
  • 3
  • 12

5 Answers5

29

One possibility is setting up the screens like this:

C' A B C A'

C' looks just like C, but when you scroll to there, it switches you to the real C. A' looks just like A, but when you scroll to there, it switches you to the real A.

I would do this by implementing onPageScrollStateChanged like so:

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

Note that this calls the alternate form of setCurrentItem and passes false to cause the jump to happen instantly rather than as a smooth scroll.

There are two main drawbacks I see to this. Firstly, upon reaching either end the user has to let the scrolling settle before they can go further. Secondly, it means having a second copy of all of the views in your first and last page. Depending on how resource-heavy your screens are, that may rule out this technique as a possible solution.

Note also that since the view pager doesn't let clicks go through to underlying controls until after the scrolling has settled, it's probably fine to not set up clicklisteners and the like for the A' and C' fragments.

Edit: Having now implemented this myself, there's another pretty major drawback. When it switches from A' to A or C' to C, the screen flickers for a moment, at least on my current test device.

benkc
  • 3,044
  • 1
  • 25
  • 37
  • Hi, could you found a way to avoid the flicker ? – RMalke Nov 15 '12 at 00:19
  • Amazing solution for my task! Thx – mbelsky Jun 30 '14 at 10:27
  • this solution a little bit slow. when scroll fast, the state not going to idle, and can't replace for example A' to A. after that the fast scrolling (crazy user) stop, the code work perfectly – sarkiroka Apr 06 '15 at 22:06
  • 1
    This is no good because there is a lag when the loop happens, please see my answer for a better solution! – MobileMon May 29 '15 at 15:13
  • if there would be two more fragment pages, what about when i set it up with a tablayout? I want my tablayout to show that there is only ABC fragments, but this will make it show 5 (C' A B C A'). – aLL Apr 25 '19 at 04:23
  • Perfect for me too! Up! – sunlover3 Oct 11 '19 at 14:22
6

I would create a dummy page at the end of the ViewPager. Then I use this code to go to the first page when the user scroll to the dummy page. I know it's far from perfect :D

@Override
public void onPageScrolled(int position, float arg1, int arg2) {
    if (position >= NUM_PAGE-1) {
        mViewPager.setCurrentItem(0, true);
    }
}
homi3kh
  • 231
  • 5
  • 17
  • 2
    if you do this way,yes you can achieve infinite scrolling but when scrolled in last page you will see the animation like going back to scren1.. – Rushi Ayyappa Jan 17 '17 at 06:46
1

My solution is based on benkc, but first and last page scroll animation are disabled, and when pages "scrolled" to real page, scroll animation is enable again, this scheme can solve the first drawback.

but my ViewPager.setCurrentItem(position, false) result is still have scroll animation, so i implements animation which is too fast to seen.

the fast scrolling animation like this, don't mind the comment, just my code didn't use these method:

public class FixedSpeedScroller extends Scroller {
    private int mDuration = 0;

    public FixedSpeedScroller(Context context) {
        super(context);
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        super.startScroll(startX, startY, dx, dy, mDuration);
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy) {
        super.startScroll(startX, startY, dx, dy, mDuration);
    }
}

and use this method to viewpager's activity

private Scroller scroller;
private void setViewPagerScroll(boolean instant) {
    try {
        Field mScroller = null;
        mScroller = ViewPager.class.getDeclaredField("mScroller");
        mScroller.setAccessible(true);
        if (scroller == null) {
            scroller = (Scroller) mScroller.get(mViewPager);
        }
        FixedSpeedScroller fss = new FixedSpeedScroller(mViewPager.getContext());
        mScroller.set(mViewPager, instant ? fss : scroller);

    } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

and modify onPageScrollStateChanged like this, only first page or last page (i have 5 pages) would change animation to fast scrolling, otherwise has normal scrolling:

public void onPageScrollStateChanged(int state) {
    if (state == ViewPager.SCROLL_STATE_IDLE) {
        if (position == 0) {
            setViewPagerScroll(true);
            mViewPager.setCurrentItem(3);

        } else if (position == 4) {
            setViewPagerScroll(true);
            mViewPager.setCurrentItem(1);

        } else {
            setViewPagerScroll(false);
        }
    }
}

FixedSpeedScroller references is here: http://blog.csdn.net/ekeuy/article/details/12841409

0

this should do the job without dummy pages:

private boolean isFirstOrLastPage;
private int currentPageIndex = 0;


@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
    if(currentPageIndex!=arg0){
        isFirstOrLastPage = false;
        return;
    }
    if((arg0==0 || arg0==PAGES.size()-1) && arg1 == 0 && arg2 == 0){
        if(isFirstOrLastPage){
                 //DO SOMETHING
        }else{
            isFirstOrLastPage = true;
        }
    }
}

@Override
public void onPageSelected(int arg0) {
    currentPageIndex = arg0;
}
0

this works, the accepted answer no good because there is a lag when the loop happens:

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

@Override
public CharSequence getPageTitle(int position) {
    String title = mTitleList.get(position % mActualTitleListSize);
    return title;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    int virtualPosition = position % mActualTitleListSize;
    return super.instantiateItem(container, virtualPosition);
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    int virtualPosition = position % mActualTitleListSize;
    super.destroyItem(container, virtualPosition, object);
}

answer taken from here : ViewPager as a circular queue / wrapping

Community
  • 1
  • 1
MobileMon
  • 7,249
  • 4
  • 46
  • 66