Changing ViewPager to enable infinite page scrolling - android

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?

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

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:

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

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

All you need to do is look at the example here
You will find that in line 295 the page is always set to 1 so that it is scrollable
and that the count of pages is 3 in getCount() method.
Those are the 2 main things you need to change, the rest is your logic and you can handle them differently.
Just make a personal counter that counts the real page you are on because position will no longer be usable after always setting current page to 1 on line 295.
p.s. this code is not mine it was referenced in the question you linked in your question

infinite slider adapter skeleton based on previous samples
some critical issues:
remember original (relative) position in page view (tag used in sample), so we will look this position to define relative position of view. otherwise child order in pager is mixed
have to fill first time absolute view inside adapter. (the rest of times this fill will be invalid) found no way to force it fill from pager handler. the rest times absolute view will be overriden from pager handler with correct values.
when pages are slided quickly, side page (actually left) is not filled from pager handler. no workaround for the moment, just use empty view, it will be filled with actual values when drag is stopped. upd: quick workaround: disable adapter's destroyItem.
you may look at the logcat to understand whats happening in this sample
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/calendar_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:padding="5dp"
android:layout_gravity="center_horizontal"
android:text="Text Text Text"
/>
</RelativeLayout>
And then:
public class ActivityCalendar extends Activity
{
public class CalendarAdapter extends PagerAdapter
{
#Override
public int getCount()
{
return 3;
}
#Override
public boolean isViewFromObject(View view, Object object)
{
return view == ((RelativeLayout) object);
}
#Override
public Object instantiateItem(ViewGroup container, int position)
{
LayoutInflater inflater = (LayoutInflater)ActivityCalendar.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View viewLayout = inflater.inflate(R.layout.layout_calendar, container, false);
viewLayout.setTag(new Integer(position));
//TextView tv = (TextView) viewLayout.findViewById(R.id.calendar_text);
//tv.setText(String.format("Text Text Text relative: %d", position));
if (!ActivityCalendar.this.scrolledOnce)
{
// fill here only first time, the rest will be overriden in pager scroll handler
switch (position)
{
case 0:
ActivityCalendar.this.setPageContent(viewLayout, globalPosition - 1);
break;
case 1:
ActivityCalendar.this.setPageContent(viewLayout, globalPosition);
break;
case 2:
ActivityCalendar.this.setPageContent(viewLayout, globalPosition + 1);
break;
}
}
((ViewPager) container).addView(viewLayout);
//Log.i("instantiateItem", String.format("position = %d", position));
return viewLayout;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object)
{
((ViewPager) container).removeView((RelativeLayout) object);
//Log.i("destroyItem", String.format("position = %d", position));
}
}
public void setPageContent(View viewLayout, int globalPosition)
{
if (viewLayout == null)
return;
TextView tv = (TextView) viewLayout.findViewById(R.id.calendar_text);
tv.setText(String.format("Text Text Text global %d", globalPosition));
}
private boolean scrolledOnce = false;
private int focusedPage = 0;
private int globalPosition = 0;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calendar);
final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setOnPageChangeListener(new OnPageChangeListener()
{
#Override
public void onPageSelected(int position)
{
focusedPage = position;
// actual page change only when position == 1
if (position == 1)
setTitle(String.format("relative: %d, global: %d", position, globalPosition));
Log.i("onPageSelected", String.format("focusedPage/position = %d, globalPosition = %d", position, globalPosition));
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
//Log.i("onPageScrolled", String.format("position = %d, positionOffset = %f", position, positionOffset));
}
#Override
public void onPageScrollStateChanged(int state)
{
Log.i("onPageScrollStateChanged", String.format("state = %d, focusedPage = %d", state, focusedPage));
if (state == ViewPager.SCROLL_STATE_IDLE)
{
if (focusedPage == 0)
globalPosition--;
else if (focusedPage == 2)
globalPosition++;
scrolledOnce = true;
for (int i = 0; i < viewPager.getChildCount(); i++)
{
final View v = viewPager.getChildAt(i);
if (v == null)
continue;
// reveal correct child position
Integer tag = (Integer)v.getTag();
if (tag == null)
continue;
switch (tag.intValue())
{
case 0:
setPageContent(v, globalPosition - 1);
break;
case 1:
setPageContent(v, globalPosition);
break;
case 2:
setPageContent(v, globalPosition + 1);
break;
}
}
Log.i("onPageScrollStateChanged", String.format("globalPosition = %d", globalPosition));
viewPager.setCurrentItem(1, false);
}
}
});
CalendarAdapter calendarAdapter = this.new CalendarAdapter();
viewPager.setAdapter(calendarAdapter);
// center item
viewPager.setCurrentItem(1, false);
}
}

Its hacked by CustomPagerAdapter:
MainActivity.java:
import android.content.Context;
import android.os.Handler;
import android.os.Parcelable;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<String> numberList = new ArrayList<String>();
private CustomPagerAdapter mCustomPagerAdapter;
private ViewPager mViewPager;
private Handler handler;
private Runnable runnable;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
numberList.clear();
for (int i = 0; i < 10; i++) {
numberList.add(""+i);
}
mViewPager = (ViewPager)findViewById(R.id.pager);
mCustomPagerAdapter = new CustomPagerAdapter(MainActivity.this);
EndlessPagerAdapter mAdapater = new EndlessPagerAdapter(mCustomPagerAdapter);
mViewPager.setAdapter(mAdapater);
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
int modulo = position%numberList.size();
Log.i("Current ViewPager View's Position", ""+modulo);
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
handler = new Handler();
runnable = new Runnable() {
#Override
public void run() {
mViewPager.setCurrentItem(mViewPager.getCurrentItem()+1);
handler.postDelayed(runnable, 1000);
}
};
handler.post(runnable);
}
#Override
protected void onDestroy() {
if(handler!=null){
handler.removeCallbacks(runnable);
}
super.onDestroy();
}
private class CustomPagerAdapter extends PagerAdapter {
Context mContext;
LayoutInflater mLayoutInflater;
public CustomPagerAdapter(Context context) {
mContext = context;
mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return numberList.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((LinearLayout) object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
View itemView = mLayoutInflater.inflate(R.layout.row_item_viewpager, container, false);
TextView textView = (TextView) itemView.findViewById(R.id.txtItem);
textView.setText(numberList.get(position));
container.addView(itemView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((LinearLayout) object);
}
}
private class EndlessPagerAdapter extends PagerAdapter {
private static final String TAG = "EndlessPagerAdapter";
private static final boolean DEBUG = false;
private final PagerAdapter mPagerAdapter;
EndlessPagerAdapter(PagerAdapter pagerAdapter) {
if (pagerAdapter == null) {
throw new IllegalArgumentException("Did you forget initialize PagerAdapter?");
}
if ((pagerAdapter instanceof FragmentPagerAdapter || pagerAdapter instanceof FragmentStatePagerAdapter) && pagerAdapter.getCount() < 3) {
throw new IllegalArgumentException("When you use FragmentPagerAdapter or FragmentStatePagerAdapter, it only supports >= 3 pages.");
}
mPagerAdapter = pagerAdapter;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (DEBUG) Log.d(TAG, "Destroy: " + getVirtualPosition(position));
mPagerAdapter.destroyItem(container, getVirtualPosition(position), object);
if (mPagerAdapter.getCount() < 4) {
mPagerAdapter.instantiateItem(container, getVirtualPosition(position));
}
}
#Override
public void finishUpdate(ViewGroup container) {
mPagerAdapter.finishUpdate(container);
}
#Override
public int getCount() {
return Integer.MAX_VALUE; // this is the magic that we can scroll infinitely.
}
#Override
public CharSequence getPageTitle(int position) {
return mPagerAdapter.getPageTitle(getVirtualPosition(position));
}
#Override
public float getPageWidth(int position) {
return mPagerAdapter.getPageWidth(getVirtualPosition(position));
}
#Override
public boolean isViewFromObject(View view, Object o) {
return mPagerAdapter.isViewFromObject(view, o);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
if (DEBUG) Log.d(TAG, "Instantiate: " + getVirtualPosition(position));
return mPagerAdapter.instantiateItem(container, getVirtualPosition(position));
}
#Override
public Parcelable saveState() {
return mPagerAdapter.saveState();
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
mPagerAdapter.restoreState(state, loader);
}
#Override
public void startUpdate(ViewGroup container) {
mPagerAdapter.startUpdate(container);
}
int getVirtualPosition(int realPosition) {
return realPosition % mPagerAdapter.getCount();
}
PagerAdapter getPagerAdapter() {
return mPagerAdapter;
}
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin" tools:context=".MainActivity">
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="180dp">
</android.support.v4.view.ViewPager>
</RelativeLayout>
row_item_viewpager.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/txtItem"
android:textAppearance="#android:style/TextAppearance.Large"/>
</LinearLayout>
Done

For infinite scrolling with days it's important you have the good fragment in the pager therefore I wrote my answer on this on page (Viewpager in Android to switch between days endlessly)
It's working very well! Above answers did not work for me as I wanted it to work.

I built a library that can make any ViewPager, pagerAdapter (or FragmentStatePagerAdapter), and optional TabLayout infinitely Scrolling.
https://github.com/memorex386/infinite-scroll-viewpager-w-tabs

Based on https://github.com/antonyt/InfiniteViewPager I wrote up this which works nicely:
class InfiniteViewPager #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewPager(context, attrs) {
// Allow for 100 back cycles from the beginning.
// This should be enough to create an illusion of infinity.
// Warning: scrolling to very high values (1,000,000+) results in strange drawing behaviour.
private val offsetAmount get() = if (adapter?.count == 0) 0 else (adapter as InfinitePagerAdapter).realCount * 100
override fun setAdapter(adapter: PagerAdapter?) {
super.setAdapter(if (adapter == null) null else InfinitePagerAdapter(adapter))
currentItem = 0
}
override fun setCurrentItem(item: Int) = setCurrentItem(item, false)
override fun setCurrentItem(item: Int, smoothScroll: Boolean) {
val adapterCount = adapter?.count
if (adapterCount == null || adapterCount == 0) {
super.setCurrentItem(item, smoothScroll)
} else {
super.setCurrentItem(offsetAmount + item % adapterCount, smoothScroll)
}
}
override fun getCurrentItem(): Int {
val adapterCount = adapter?.count
return if (adapterCount == null || adapterCount == 0) {
super.getCurrentItem()
} else {
val position = super.getCurrentItem()
position % (adapter as InfinitePagerAdapter).realCount
}
}
fun animateForward() {
super.setCurrentItem(super.getCurrentItem() + 1, true)
}
fun animateBackwards() {
super.setCurrentItem(super.getCurrentItem() - 1, true)
}
internal class InfinitePagerAdapter(private val adapter: PagerAdapter) : PagerAdapter() {
internal val realCount: Int get() = adapter.count
override fun getCount() = if (realCount == 0) 0 else Integer.MAX_VALUE
override fun instantiateItem(container: ViewGroup, position: Int) = adapter.instantiateItem(container, position % realCount)
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) = adapter.destroyItem(container, position % realCount, `object`)
override fun finishUpdate(container: ViewGroup) = adapter.finishUpdate(container)
override fun isViewFromObject(view: View, `object`: Any) = adapter.isViewFromObject(view, `object`)
override fun restoreState(bundle: Parcelable?, classLoader: ClassLoader?) = adapter.restoreState(bundle, classLoader)
override fun saveState(): Parcelable? = adapter.saveState()
override fun startUpdate(container: ViewGroup) = adapter.startUpdate(container)
override fun getPageTitle(position: Int) = adapter.getPageTitle(position % realCount)
override fun getPageWidth(position: Int) = adapter.getPageWidth(position)
override fun setPrimaryItem(container: ViewGroup, position: Int, `object`: Any) = adapter.setPrimaryItem(container, position, `object`)
override fun unregisterDataSetObserver(observer: DataSetObserver) = adapter.unregisterDataSetObserver(observer)
override fun registerDataSetObserver(observer: DataSetObserver) = adapter.registerDataSetObserver(observer)
override fun notifyDataSetChanged() = adapter.notifyDataSetChanged()
override fun getItemPosition(`object`: Any) = adapter.getItemPosition(`object`)
}
}
For consuming it simply change your ViewPager to InfiniteViewPager and that's all you need to change.

Related

ViewPager + PagerAdapter w/ Pagination not working properly

I have a gallery/slideshow activity that allows user to swipe between Photos. Each "Page" is just a TouchImageView. There is a pagination logic in there, and I can see that it is calling the API accordingly. However, I am not able to swipe further even after notifyDataSetChanged has been called. Here's the code:
Activity {
ViewPager vp;
CustomPagerAdapter pagerAdapter;
onCreate() {
//api callback
pagerAdapter = new CustomPagerAdapter();
vp.setAdapter(pagerAdapter);
}
}
CustomPagerAdapter() {
TouchImageView imageView;
List<Photos> photos;
int getCount() {
return photos == null ? 0 : photos.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
View itemView = mLayoutInflater.inflate(R.layout.pager_image, container, false);
imageView = (TouchImageView) itemView.findViewById(R.id.photo);
Glide.with(context)
.load(photos.get(position).getUrl())
.into(imageView);
container.addView(itemView);
if (position == photos.size()-1) {
loadMorePhotos();
}
return itemView;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((RelativeLayout) object);
}
void loadMorePhotos() {
//call api and stuff
void onResponse(Call call, Response<List<Photo>> response) {
photos.addAll(response.body);
notifyDataSetChanged();
}
}
}
And this is the result:
http://imgur.com/nA1TzfI
I have no idea what is going on for 2 days. Please help!
This may not be ideal, but where you call notifyDatasetChanged() you could instead try calling vp.setAdapter(pagerAdapter);
(i.e. actually re-set the adapter). A downside of this is that the pager will probably go back to having the first page selected. To avoid this, you'd need to save the latest page somewhere and restore it afterwards.
As I say, not ideal, but it may help.
You dont have to load more photos from adapter class, in your activity class you can use onPageScrolled and call the api when the last item is scrolled and set adapter again also save state of view pager before calling api to retain it when adapter is set again.
private boolean isLastPageSwiped;
private int counterPageScroll;
private OnPageChangeListener mListener = new OnPageChangeListener() {
#Override
public void onPageSelected(int arg0) {
// TODO Auto-generated method stub
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
if (position == 6 && positionOffset == 0 && !isLastPageSwiped){
if(counterPageScroll != 0){
isLastPageSwiped=true;
//call api
}
counterPageScroll++;
}else{
counterPageScroll=0;
}
}
#Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
};
and use this listener
vp.setOnPageChangeListener(mListener);
We can use this approach for doing pagination on ViewPager (with fragment)
load new page data but not append to the adapter
make sure the page is not scrolling, then append new page data into the adapter (ViewPager.OnPageChangeListener#onPageScrolled's positionOffsetPixels == 0).
update current item for view pager with offset = new page data's size (let's call it newPosition)
rebind data to 3 fragments: newPosition - 1, newPosition, newPosition + 1
For getting current fragment, we can use fragment manager
private fun getFragment(position: Int): PageFragment? =
supportFragmentManager.fragments.firstOrNull {
(it as? PageFragment)?.position == position // always pass possition value into the fragment
} as? PageFragment
We need to append data when not scrolling (step 2) to have a better UX to users. If we append data immediately when it comes and if user is scrolling, view pager's setCurrentItem will make a strange move.
I create a simple version here https://github.com/tuanchauict/DemoPaginationViewPager. Please take a look

How can I find out which image is the current one in a ViewPager?

I'm fairly new to Android programming, and I'm trying to design an app where when I swipe through several images on a ViewPager that takes up some of the screen, other elements of that same activity/screen will change with it without necessarily 'scrolling' horizontally like the ViewPager does. My problem is that I can't find a way to identify which image in the ViewPager is the 'current' one outside of the ViewPager.
I tried to create a getter for grabbing the position from instantiateItem method, but no luck - because I assume it simply creates everything once, not updating it again, so when I swipe nothing will happen. Also, I realize that my dieValue variable doesn't do anything - but it's meant to serve as an example of what I want to accomplish. The dieValue variable would change based on which image was the current one.
CustomSwipeAdapter.java
public class CustomSwipeAdapter extends PagerAdapter {
private Context ctx;
public int[] image_resources = {R.drawable.d20, R.drawable.d8, R.drawable.d6};
public CustomSwipeAdapter(Context ctx) {
this.ctx = ctx;
}
#Override
public int getCount() {
return image_resources.length;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater layoutInflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View item_view = layoutInflater.inflate(R.layout.swipe_layout, container, false);
ImageView imageView = (ImageView) item_view.findViewById(R.id.dieImageView);
imageView.setImageResource(image_resources[position]);
TextView textView = (TextView) item_view.findViewById(R.id.dieValueTop);
if (position == 0) { textView.setText(R.string.d20Label); }
if (position == 1) { textView.setText(R.string.d8Label); }
if (position == 2) { textView.setText(R.string.d6Label); }
container.addView(item_view);
return item_view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((LinearLayout) object);
}
}
DiceRollerActivity.java
public class DiceRollerActivity extends Activity implements View.OnClickListener {
ViewPager viewPager;
CustomSwipeAdapter adapter;
private int dieValue;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dice_roller);
viewPager = (ViewPager) findViewById(R.id.dieViewPager);
adapter = new CustomSwipeAdapter(this);
viewPager.setAdapter(adapter);
Button RollDieButton = (Button) findViewById(R.id.rollDieButton);
RollDieButton.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.rollDieButton:
Random rand = new Random();
int random = rand.nextInt(dieValue) + 1;
setDieResults(random);
}
}
// Prints the results of the die roll to the results log
private void setDieResults(int random) {
TextView dieResults = (TextView) findViewById(R.id.dieResultsLabel);
if (random == dieValue) {
dieResults.setText(getString(R.string.YouRolledLabel) + random + getString(R.string.criticalHit));
} else if (random == dieValue) {
dieResults.setText(getString(R.string.YouRolledLabel) + random + getString(R.string.criticalMiss));
}else{
dieResults.setText(getString(R.string.YouRolledLabel) + random);
}
}
}
Create a method inside your CustomSwipeAdapter
public int getCurrentImageResource(int currentPosition){
return image_resources[currentPosition];
}
To access to the current image call this method passing current position with viewPager.getCurrentItem() like this
adapter.getCurrentImageResource(viewPager.getCurrentItem());
Hope this helps!
You could use
viewPager.getCurrentItem();
on your DiceRollerActivity.class to get the current position.
Then, to get the image a better approach would be to have the image array on your DiceRollerActivity.class and pass it onto the CustomSwipeAdapter constructor.

ViewPager not showing the right page

I am trying to implement a ViewPager without using Fragments. Let's say, I have an unspecific amount of data stored inside some kind of database which is mapped to specific days. What I am trying to do is to display the data for a specific day on ViewPager pages.
This is what I am currently doing:
#Override
protected void onCreate(Bundle savedInstanceState) {
dataList = (ViewPager) findViewById(R.id.dataList);
dataList.setAdapter(new DataListSlidePagerAdapter());
dataList.setCurrentItem(Integer.MAX_VALUE / 2);
}
private class DataListSlidePagerAdapter extends PagerAdapter {
int lastPosition;
#Override
public Object instantiateItem(ViewGroup collection, int position) {
if (position > lastPosition) {
GregorianCalendar today = Settings.getCurrentDay();
today.add(GregorianCalendar.DATE, 1);
Settings.loadData();
}
else if (position < lastPosition) {
GregorianCalendar today = Settings.getCurrentDay();
today.add(GregorianCalendar.DATE, -1);
Settings.loadData();
}
lastPosition = position;
LinearLayout v = createDataList(Settings.getTodaysData());
collection.addView(v);
return v;
}
#Override
public void destroyItem(ViewGroup collection, int position, Object view) {
((ViewPager) collection).removeView((LinearLayout) view);
}
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == ((View) arg1);
}
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
This actually almost works, but unfortunately, the data for the current day is never shown on the current page, but rather on the next or previous page. My guess would be, that this is because instantiateItem does not necessarily build the view of the current page, but rather of some page which is probably needed to be shown next. createListData(), however, always creates the view of the current day.
Any idea how to fix this and to display the right data on the right pages? You can assume, that there are also functions like Settings.getTomorrowsData() and Settings.getYesterdaysData().

Calling notifyDataSetChanged from instantiateItem()

I have a requirement to show a ViewPager displaying initially just one View.
Once this View goes through measure & layout pass, depending on the result of these I might need to change the contents of the adapter.
While doing so, I've come across a scenario where the ViewPager will not follow through with it's movement while trying to swipe to the next item from a certain position.
I'm sure I'm doing something wrong in my adapter implementation, yet I can't point my finger at the problem.
For the sake of demonstration I've created a dummy adapter that will mimic what happens on my actual project.
In order to reproduce the issue just try to swipe all the way to position 2 (3rd item), result is you can't get past position 1 (2nd item).
Thanks!
public class CustomAdapter extends PagerAdapter {
int pages = 0;
#Override
public int getCount() {
return pages;
}
#Override
public boolean isViewFromObject(View view, Object o) {
return view == o;
}
public void setPages(int x) {
this.pages = x;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
TextView tv = new TextView(MainActivity.this);
ViewGroup.LayoutParams lp =
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
tv.setLayoutParams(lp);
tv.setText("Position: " + position);
if (position == 0 && pages == 1) {
setPages(2);
notifyDataSetChanged();
}
if (position == 1 && pages == 2) {
setPages(3);
notifyDataSetChanged();
}
container.addView(tv);
return tv;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
Activity (onCreate or so):
CustomAdapter adapter = new CustomAdapter();
adapter.setPages(1);
vp.setAdapter(adapter);
adapter.notifyDataSetChanged();
instantiateItem item method is wrong place to do it. because it doest work for only one view it works for 3 view at same time.
try moving
if (position == 0 && pages == 1) {
setPages(2);
notifyDataSetChanged();
}
if (position == 1 && pages == 2) {
setPages(3);
notifyDataSetChanged();
}
in
setPrimaryItem Method
UPDATE:
Try to call instantiateItem method for the position you want in the setPrimaryItem method. You can do it since instantiateItem is a public method.But i have no idea how it is going to work it is a theory. Just give it a try.

Adding/Removing views for PagerAdapter / ViewPager issue Android

I would like to keep a list of 3 views at all times. The app starts at position 1 (out of positions 0,1,2). When someone scrolls to position 0, I would like to remove view 2, and create a view before position 0. This way, it appears to the user, that there are unlimited views. In the same way, when someone scrolls to position 2, I would like to remove the view at position 0 and add one at the end.
However I'm having problems with both adding and removing views. When I get to position 0, nothing changes unless I try scrolling past position 0 (to position -1, i.e. the boundary is hit). At that point, I can see that it is the boundary of my views, but then setCurrentItem(1,false) is triggered and I'm brought back to the middle of the views. When I scroll to position 2 I see that position 2 has been updated. However position 0 and 1 remain the same.
When I scroll to position 2, nothing happens. However if I try and scroll to the boundary, for some reason, position 0 gets updated and setCurrentItem(1,false) is triggered.
I have no idea why its happening like this. Can anyone shed some light on this?
Here is my code:
public class MainActivity extends Activity {
ArrayList<Integer> showThree = new ArrayList<Integer>();
int focusedPage = 0;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
showThree.add(0,5); //adding integers 5,6,7 for positions 0,1,2
showThree.add(1,6);
showThree.add(2,7);
final MyPagerAdapter adapter = new MyPagerAdapter(getApplicationContext(),showThree);
final ViewPager myPager = (ViewPager) findViewById(R.id.mypanelpager);
myPager.setAdapter(adapter);
myPager.setCurrentItem(1);
myPager.setOnPageChangeListener(new OnPageChangeListener(){
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
//when position= 0, change the 3 views from 5,6,7 to 4,5,6.
if (focusedPage == 0) {
showThreeMonths.set(0,4);
showThreeMonths.set(1,5);
showThreeMonths.set(2,6);
adapter.notifyDataSetChanged();
adapter.startUpdate(myPager);
}
else if (focusedPage ==2){
//ignore, just testing focusPage=0 for now }
}
//set current page to the middle of the 3 new views, which would be
//the same view at position 0 of the old 3 views.
//Thus user doesn't experience the views changing despite being 3 new views.
myPager.setCurrentItem(1,false);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// TODO Auto-generated method stub
}
#Override
public void onPageSelected(int position) {
focusedPage = position;
}
});
}
PagerAdapter
public class MyPagerAdapter extends PagerAdapter {
private ArrayList<Integer> showThreeMonths;
private Context ctx;
public MyPagerAdapter (Context ctx, ArrayList<Integer> showThree){
this.ctx = ctx ;
this.showThree = showThree;
}
#Override
public int getCount() {
return showThree.size();
}
public Object instantiateItem(ViewGroup collection, int position ){
//NewCustomView is a class I made that takes parameters context and an integer and creates a view based on the integer
NewCustomView MyOwnView = new NewCustomView(ctx, showThree.get(position));
View customViewLayout = MyOwnView.newLayout; //part of the class object
collection.addView(customViewLayout);
return customViewLayout;
}
#Override
public void destroyItem(ViewGroup collection, int position, Object arg2) {
((ViewPager) collection).removeView((ViewGroup) arg2);}
#Override
public Parcelable saveState() {
return null;}
#Override
public boolean isViewFromObject(View view, Object arg1) {
return view==arg1;}
#Override
public void startUpdate(ViewGroup collection) {}
#Override
public void finishUpdate(ViewGroup collection) {}
}
The instantiateItem() method creates the 2 view pages in the memory by default. Therefore when you swipe to the second page then 0 page is recreated as it's outside the range of the 2 pages saved in the memory. Please try to use
myViewPager.setOffscreenPageLimit(numberOfPages)
method that receives an integer as a parameter and declares how many pages it should be keeping before recycling them.

Categories

Resources