Changing ViewPager to enable infinite page scrolling
Jon Willis has posted on how to enable an infinite scrolling with his code.
In there he said that he made some changes in the ViewPager
class in the android support library. Which changes have been made and how is it possible to "recompile" the library with the ViewPager
change?
Solution 1:
I solved this problem very simply using a little hack in the adapter. Here is my code:
public class MyPagerAdapter extends FragmentStatePagerAdapter
{
public static int LOOPS_COUNT = 1000;
private ArrayList<Product> mProducts;
public MyPagerAdapter(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(); // use modulo for infinite cycling
return MyFragment.newInstance(mProducts.get(position));
}
else
{
return MyFragment.newInstance(null);
}
}
@Override
public int getCount()
{
if (mProducts != null && mProducts.size() > 0)
{
return mProducts.size()*LOOPS_COUNT; // simulate infinite by big number of products
}
else
{
return 1;
}
}
}
And then, in the ViewPager, we set current page to the middle:
mAdapter = new MyPagerAdapter(getSupportFragmentManager(), mProducts);
mViewPager.setAdapter(mAdapter);
mViewPager.setCurrentItem(mViewPager.getChildCount() * MyPagerAdapter.LOOPS_COUNT / 2, false); // set current item in the adapter to middle
Solution 2:
Thank you for your answer Shereef.
I solved it a little bit differently.
I changed the code of the ViewPager class of the android support library. The method setCurrentItem(int)
changes the page with animation. This method calls an internal method that requires the index and a flag enabling smooth scrolling. This flag is boolean smoothScroll
.
Extending this method with a second parameter boolean smoothScroll
solved it for me.
Calling this method setCurrentItem(int index, boolean smoothScroll)
allowed me to make it scroll indefinitely.
Here is a full example:
Please consider that only the center page is shown. Moreover did I store the pages seperately, allowing me to handle them with more ease.
private class Page {
View page;
List<..> data;
}
// page for predecessor, current, and successor
Page[] pages = new Page[3];
mDayPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
if (mFocusedPage == 0) {
// move some stuff from the
// center to the right here
moveStuff(pages[1], pages[2]);
// move stuff from the left to the center
moveStuff(pages[0], pages[1]);
// retrieve new stuff and insert it to the left page
insertStuff(pages[0]);
}
else if (mFocusedPage == 2) {
// move stuff from the center to the left page
moveStuff(pages[1], pages[0]);
// move stuff from the right to the center page
moveStuff(pages[2], pages[1]);
// retrieve stuff and insert it to the right page
insertStuff(pages[2]);
}
// go back to the center allowing to scroll indefinitely
mDayPager.setCurrentItem(1, false);
}
}
});
However, without Jon Willis Code I wouldn't have solved it myself.
EDIT: here is a blogpost about this:
Solution 3:
Infinite view pager by overriding 4 adapter methods in your existing adapter class
@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);
}
Solution 4:
Actually, I've been looking at the various ways to do this "infinite" pagination, and even though the human notion of time is that it is infinite (even though we have a notion of the beginning and end of time), computers deal in the discrete. There is a minimum and maximum time (that can be adjusted as time goes on, remember the basis of the Y2K scare?).
Anyways, the point of this discussion is that it is/should be sufficient to support a relatively infinite date range through an actually finite date range. A great example of this is the Android framework's CalendarView
implementation, and the WeeksAdapter
within it. The default minimum date is in 1900 and the default maximum date is in 2100, this should cover 99% of the calendar use of anyone within a 10 year radius around today easily.
What they do in their implementation (focused on weeks) is compute the number of weeks between the minimum and maximum date. This becomes the number of pages in the pager. Remember that the pager doesn't need to maintain all of these pages simultaneously (setOffscreenPageLimit(int)
), it just needs to be able to create the page based on the page number (or index/position). In this case the index is the number of weeks that the week is from the minimum date. With this approach you just have to maintain the minimum date and the number of pages (distance to the maximum date), then for any page you can easily compute the week associated with that page. No dancing around the fact that ViewPager
doesn't support looping (a.k.a infinite pagination), and trying to force it to behave like it can scroll infinitely.
new FragmentStatePagerAdapter(getFragmentManager()) {
@Override
public Fragment getItem(int index) {
final Bundle arguments = new Bundle(getArguments());
final Calendar temp_calendar = Calendar.getInstance();
temp_calendar.setTimeInMillis(_minimum_date.getTimeInMillis());
temp_calendar.setFirstDayOfWeek(_calendar.getStartOfWeek());
temp_calendar.add(Calendar.WEEK_OF_YEAR, index);
// Moves to the first day of this week
temp_calendar.add(Calendar.DAY_OF_YEAR,
-UiUtils.modulus(temp_calendar.get(Calendar.DAY_OF_WEEK) - temp_calendar.getFirstDayOfWeek(),
7));
arguments.putLong(KEY_DATE, temp_calendar.getTimeInMillis());
return Fragment.instantiate(getActivity(), WeekDaysFragment.class.getName(), arguments);
}
@Override
public int getCount() {
return _total_number_of_weeks;
}
};
Then WeekDaysFragment
can easily display the week starting at the date passed in its arguments.
Alternatively, it seems that some version of the Calendar app on Android uses a ViewSwitcher
(which means there's only 2 pages, the one you see and the hidden page). It then changes the transition animation based on which way the user swiped and renders the next/previous page accordingly. In this way you get infinite pagination because it just switching between two pages infinitely. This requires using a View
for the page though, which is way I went with the first approach.
In general, if you want "infinite pagination", it's probably because your pages are based off of dates or times somehow. If this is the case consider using a finite subset of time that is relatively infinite instead. This is how CalendarView
is implemented for example. Or you can use the ViewSwitcher
approach. The advantage of these two approaches is that neither does anything particularly unusual with the ViewSwitcher
or ViewPager
, and doesn't require any tricks or reimplementation to coerce them to behave infinitely (ViewSwitcher
is already designed to switch between views infinitely, but ViewPager
is designed to work on a finite, but not necessarily constant, set of pages).