diff --git a/LoopingViewPager/AndroidManifest.xml b/LoopingViewPager/AndroidManifest.xml
new file mode 100644
index 000000000..53181016a
--- /dev/null
+++ b/LoopingViewPager/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/LoopingViewPager/README.md b/LoopingViewPager/README.md
new file mode 100644
index 000000000..9777790e6
--- /dev/null
+++ b/LoopingViewPager/README.md
@@ -0,0 +1,56 @@
+LoopingViewPager
+================
+
+An android ViewPager extension allowing infinite scrolling.
+
+This viewPager is fully compatible with [ViewPagerIndicator][1], and [PagerSlidingTabStrip][2]!
+
+
+
+Usage
+-----
+
+To use it simply change `` to ``
+
+I your PagerAdapter is used only to create Views (i.e. you don't use FragmentPagerAdapter or FragmentStatePagerAdapter),
+then no additional code changes are needed!
+
+
+If you want to use LoopViewPager with FragmentPagerAdapter or FragmentStatePagerAdapter
+additional changes in the adapter must be done.
+The adapter must be prepared to create 2 extra items e.g.:
+
+The original adapter creates 4 items: `[0,1,2,3]`
+The modified adapter will have to create 6 items `[0,1,2,3,4,5]`
+with mapping `realPosition=(position-1)%count`
+`[0->3, 1->0, 2->1, 3->2, 4->3, 5->0]`
+
+
+Sometimes "blinking" can be seen when paginating to first or last view.
+To remove this effect, simply call setBoundaryCaching( true ) on your LoopViewPager,
+or change DEFAULT_BOUNDARY_CASHING to true, if you want to set boundary caching
+on all LoopViewPager instances
+
+
+
+License
+=======
+
+ Copyright 2013 Leszek Mzyk
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+
+ [1]: https://github.com/JakeWharton/Android-ViewPagerIndicator
+ [2]: https://github.com/astuetz/PagerSlidingTabStrip
\ No newline at end of file
diff --git a/LoopingViewPager/libs/android-support-v4.jar b/LoopingViewPager/libs/android-support-v4.jar
new file mode 100644
index 000000000..cf12d2839
Binary files /dev/null and b/LoopingViewPager/libs/android-support-v4.jar differ
diff --git a/LoopingViewPager/project.properties b/LoopingViewPager/project.properties
new file mode 100644
index 000000000..4a46b9d1c
--- /dev/null
+++ b/LoopingViewPager/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+android.library=true
+# Project target.
+target=android-17
diff --git a/LoopingViewPager/src/com/imbryk/viewPager/LoopPagerAdapterWrapper.java b/LoopingViewPager/src/com/imbryk/viewPager/LoopPagerAdapterWrapper.java
new file mode 100644
index 000000000..7b29e2b43
--- /dev/null
+++ b/LoopingViewPager/src/com/imbryk/viewPager/LoopPagerAdapterWrapper.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2013 Leszek Mzyk
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.imbryk.viewPager;
+
+import android.os.Parcelable;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.view.PagerAdapter;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A PagerAdapter wrapper responsible for providing a proper page to
+ * LoopViewPager
+ *
+ * This class shouldn't be used directly
+ */
+public class LoopPagerAdapterWrapper extends PagerAdapter {
+
+ private PagerAdapter mAdapter;
+
+ private SparseArray mToDestroy = new SparseArray();
+
+ private boolean mBoundaryCaching;
+
+ void setBoundaryCaching(boolean flag) {
+ mBoundaryCaching = flag;
+ }
+
+ LoopPagerAdapterWrapper(PagerAdapter adapter) {
+ this.mAdapter = adapter;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ mToDestroy = new SparseArray();
+ super.notifyDataSetChanged();
+ }
+
+ int toRealPosition(int position) {
+ int realCount = getRealCount();
+
+ int realPosition = (position-1) % realCount;
+ if (realPosition < 0)
+ realPosition += realCount;
+
+ return realPosition;
+ }
+
+ public int toInnerPosition(int realPosition) {
+ int position = (realPosition + 1);
+ return position;
+ }
+
+ private int getRealFirstPosition() {
+ return 1;
+ }
+
+ private int getRealLastPosition() {
+ return getRealFirstPosition() + getRealCount() - 1;
+ }
+
+ @Override
+ public int getCount() {
+ return mAdapter.getCount() + 2;
+ }
+
+ public int getRealCount() {
+ return mAdapter.getCount();
+ }
+
+ public PagerAdapter getRealAdapter() {
+ return mAdapter;
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ int realPosition = (mAdapter instanceof FragmentPagerAdapter || mAdapter instanceof FragmentStatePagerAdapter)
+ ? position
+ : toRealPosition(position);
+
+ if (mBoundaryCaching) {
+ ToDestroy toDestroy = mToDestroy.get(position);
+ if (toDestroy != null) {
+ mToDestroy.remove(position);
+ return toDestroy.object;
+ }
+ }
+ return mAdapter.instantiateItem(container, realPosition);
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ int realFirst = getRealFirstPosition();
+ int realLast = getRealLastPosition();
+ int realPosition = (mAdapter instanceof FragmentPagerAdapter || mAdapter instanceof FragmentStatePagerAdapter)
+ ? position
+ : toRealPosition(position);
+
+ if (mBoundaryCaching && (position == realFirst || position == realLast)) {
+ mToDestroy.put(position, new ToDestroy(container, realPosition,
+ object));
+ } else {
+ mAdapter.destroyItem(container, realPosition, object);
+ }
+ }
+
+ /*
+ * Delegate rest of methods directly to the inner adapter.
+ */
+
+ @Override
+ public void finishUpdate(ViewGroup container) {
+ mAdapter.finishUpdate(container);
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return mAdapter.isViewFromObject(view, object);
+ }
+
+ @Override
+ public void restoreState(Parcelable bundle, ClassLoader classLoader) {
+ mAdapter.restoreState(bundle, classLoader);
+ }
+
+ @Override
+ public Parcelable saveState() {
+ return mAdapter.saveState();
+ }
+
+ @Override
+ public void startUpdate(ViewGroup container) {
+ mAdapter.startUpdate(container);
+ }
+
+ /*
+ * End delegation
+ */
+
+ /**
+ * Container class for caching the boundary views
+ */
+ static class ToDestroy {
+ ViewGroup container;
+ int position;
+ Object object;
+
+ public ToDestroy(ViewGroup container, int position, Object object) {
+ this.container = container;
+ this.position = position;
+ this.object = object;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/LoopingViewPager/src/com/imbryk/viewPager/LoopViewPager.java b/LoopingViewPager/src/com/imbryk/viewPager/LoopViewPager.java
new file mode 100644
index 000000000..2feccbc2e
--- /dev/null
+++ b/LoopingViewPager/src/com/imbryk/viewPager/LoopViewPager.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2013 Leszek Mzyk
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.imbryk.viewPager;
+
+import android.content.Context;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+
+/**
+ * A ViewPager subclass enabling infinte scrolling of the viewPager elements
+ *
+ * When used for paginating views (in opposite to fragments), no code changes
+ * should be needed only change xml's from
+ * to
+ *
+ * If "blinking" can be seen when paginating to first or last view, simply call
+ * seBoundaryCaching( true ), or change DEFAULT_BOUNDARY_CASHING to true
+ *
+ * When using a FragmentPagerAdapter or FragmentStatePagerAdapter,
+ * additional changes in the adapter must be done.
+ * The adapter must be prepared to create 2 extra items e.g.:
+ *
+ * The original adapter creates 4 items: [0,1,2,3]
+ * The modified adapter will have to create 6 items [0,1,2,3,4,5]
+ * with mapping realPosition=(position-1)%count
+ * [0->3, 1->0, 2->1, 3->2, 4->3, 5->0]
+ */
+public class LoopViewPager extends ViewPager {
+
+ private static final boolean DEFAULT_BOUNDARY_CASHING = false;
+
+ OnPageChangeListener mOuterPageChangeListener;
+ private LoopPagerAdapterWrapper mAdapter;
+ private boolean mBoundaryCaching = DEFAULT_BOUNDARY_CASHING;
+
+
+ /**
+ * helper function which may be used when implementing FragmentPagerAdapter
+ *
+ * @param position
+ * @param count
+ * @return (position-1)%count
+ */
+ public static int toRealPosition( int position, int count ){
+ position = position-1;
+ if( position < 0 ){
+ position += count;
+ }else{
+ position = position%count;
+ }
+ return position;
+ }
+
+ /**
+ * If set to true, the boundary views (i.e. first and last) will never be destroyed
+ * This may help to prevent "blinking" of some views
+ *
+ * @param flag
+ */
+ public void setBoundaryCaching(boolean flag) {
+ mBoundaryCaching = flag;
+ if (mAdapter != null) {
+ mAdapter.setBoundaryCaching(flag);
+ }
+ }
+
+ @Override
+ public void setAdapter(PagerAdapter adapter) {
+ mAdapter = new LoopPagerAdapterWrapper(adapter);
+ mAdapter.setBoundaryCaching(mBoundaryCaching);
+ super.setAdapter(mAdapter);
+ setCurrentItem(0, false);
+ }
+
+ @Override
+ public PagerAdapter getAdapter() {
+ return mAdapter != null ? mAdapter.getRealAdapter() : mAdapter;
+ }
+
+ @Override
+ public int getCurrentItem() {
+ return mAdapter != null ? mAdapter.toRealPosition(super.getCurrentItem()) : 0;
+ }
+
+ public void setCurrentItem(int item, boolean smoothScroll) {
+ int realItem = mAdapter.toInnerPosition(item);
+ super.setCurrentItem(realItem, smoothScroll);
+ }
+
+ @Override
+ public void setCurrentItem(int item) {
+ if (getCurrentItem() != item) {
+ setCurrentItem(item, true);
+ }
+ }
+
+ @Override
+ public void setOnPageChangeListener(OnPageChangeListener listener) {
+ mOuterPageChangeListener = listener;
+ };
+
+ public LoopViewPager(Context context) {
+ super(context);
+ init();
+ }
+
+ public LoopViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ super.setOnPageChangeListener(onPageChangeListener);
+ }
+
+ private OnPageChangeListener onPageChangeListener = new OnPageChangeListener() {
+ private float mPreviousOffset = -1;
+ private float mPreviousPosition = -1;
+
+ @Override
+ public void onPageSelected(int position) {
+
+ int realPosition = mAdapter.toRealPosition(position);
+ if (mPreviousPosition != realPosition) {
+ mPreviousPosition = realPosition;
+ if (mOuterPageChangeListener != null) {
+ mOuterPageChangeListener.onPageSelected(realPosition);
+ }
+ }
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset,
+ int positionOffsetPixels) {
+ int realPosition = position;
+ if (mAdapter != null) {
+ realPosition = mAdapter.toRealPosition(position);
+
+ if (positionOffset == 0
+ && mPreviousOffset == 0
+ && (position == 0 || position == mAdapter.getCount() - 1)) {
+ setCurrentItem(realPosition, false);
+ }
+ }
+
+ mPreviousOffset = positionOffset;
+ if (mOuterPageChangeListener != null) {
+ if (realPosition != mAdapter.getRealCount() - 1) {
+ mOuterPageChangeListener.onPageScrolled(realPosition,
+ positionOffset, positionOffsetPixels);
+ } else {
+ if (positionOffset > .5) {
+ mOuterPageChangeListener.onPageScrolled(0, 0, 0);
+ } else {
+ mOuterPageChangeListener.onPageScrolled(realPosition,
+ 0, 0);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ if (mAdapter != null) {
+ int position = LoopViewPager.super.getCurrentItem();
+ int realPosition = mAdapter.toRealPosition(position);
+ if (state == ViewPager.SCROLL_STATE_IDLE
+ && (position == 0 || position == mAdapter.getCount() - 1)) {
+ setCurrentItem(realPosition, false);
+ }
+ }
+ if (mOuterPageChangeListener != null) {
+ mOuterPageChangeListener.onPageScrollStateChanged(state);
+ }
+ }
+ };
+
+}
diff --git a/library/libs/android-support-v4.jar b/library/libs/android-support-v4.jar
index 99e063b33..cf12d2839 100644
Binary files a/library/libs/android-support-v4.jar and b/library/libs/android-support-v4.jar differ
diff --git a/sample/libs/android-support-v4.jar b/sample/libs/android-support-v4.jar
index 99e063b33..cf12d2839 100644
Binary files a/sample/libs/android-support-v4.jar and b/sample/libs/android-support-v4.jar differ
diff --git a/sample/project.properties b/sample/project.properties
index 874eafdf2..198a012d9 100644
--- a/sample/project.properties
+++ b/sample/project.properties
@@ -10,3 +10,4 @@
# Project target.
target=android-16
android.library.reference.1=../library
+android.library.reference.2=../LoopingViewPager
diff --git a/sample/res/layout/simple_circles.xml b/sample/res/layout/simple_circles.xml
index c5da13800..d640ed42b 100644
--- a/sample/res/layout/simple_circles.xml
+++ b/sample/res/layout/simple_circles.xml
@@ -20,7 +20,7 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent">
-
-
-
-
-
-
-
-
-
-
-