How to do circular scrolling on ViewPager?
Solution 1
Ok, I have an answer. It was actually easier than I expected, but it does take some trickery. First, let me begin with the set up. Lets say, for example, you have three pages (A-B-C) that you are scrolling through in your ViewPager. And you want to set it up so that if you continue scrolling on C (pg. 3), it goes to A (pg. 1) and if you scrolled backwards on A (pg. 1) it goes to C (pg. 3).
I am not saying my solution is the best, but it works and I do not see any issues. First, you have to create two "fake" pages. The fake pages represent the first and last pages of your ViewPager. The next thing you will need is to set up an onPageChangeListener(), and use the method onPageSelected(). The reason why you need the fake pages is because onPageSelected() only registers after you have moved (swiped). In other words, without this method the end user would have to scroll to page 2 and back to page 1 to receive a hit on page 1, which also means that page 1 would be skipped depending on your code logic.
The setup is really the entire answer. Once you have your fake pages, it is just a matter of using setCurrentItem() inside the necessary method.
Here is how my code looks. Be sure to place this inside your public Object instantiateItem(final ViewGroup container, final int position) method, just before you return your view inside of your container.
((ViewPager) container).setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
Log.d(TAG, "onPageSelected() :: " + "position: " + position);
// skip fake page (first), go to last page
if (position == 0) {
((ViewPager) container).setCurrentItem(118, false);
}
// skip fake page (last), go to first page
if (position == 119) {
((ViewPager) container).setCurrentItem(1, false); //notice how this jumps to position 1, and not position 0. Position 0 is the fake page!
}
}
That's it, it does the trick! The only other thing to do is start your ViewPager on position 1 (which is the second page: fake page = pg 1, my real starting page = pg 2). Now, every time I scroll to the fake page, I redirect it backwards to the last real page. And every time I scroll forward to the last fake page, I redirect it forwards to the real starting page (pg 2).
Also, do not try to put any code in onPageScrollStateChanged. That method is bizarre, it seems that the state value is uncontrollable. It constantly jumps from one state to another. Even without scrolling. That is just a tip I picked up.
Solution 2
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 && mScrollState == ViewPager.SCROLL_STATE_DRAGGING) {
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, true);
} else if(mCurrentPosition == lastPosition) {
mViewPager.setCurrentItem(0, true);
}
}
@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:
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"
Solution 3
I have created the circular viewpager with smooth scroll from last to first in swipe left and first to last during swipe right.
for this add the last page in the starting and the first page to the last: inside addOnPageChangeListener : we have to do some calculation , when we are 0 position then on onPageScrollStateChanged set the current item as the last item and vise-versa.
Have a look to the code
public class ViewPagerCircular_new extends AppCompatActivity {
ViewPager viewPager;
ArrayList<String> str = new ArrayList<String>();
boolean chageImage = false;
int setPos;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.view_pager_normal);
viewPager = (ViewPager) findViewById(R.id.vf_home_top_pager);
str.add("6"); // added the last page to the frist
str.add("1"); // First item to display in view pager
str.add("2");
str.add("3");
str.add("4");
str.add("5");
str.add("6"); // last item to display in view pager
str.add("1"); // added the first page to the last
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener()
{
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
if (position == str.size() - 1)
{
chageImage = true;
setPos = 1;
} else if (position == 0)
{
chageImage = true;
setPos = str.size() - 2;
} else
{
chageImage = false;
}
}
@Override
public void onPageSelected(int position)
{
}
@Override
public void onPageScrollStateChanged(int state)
{
if (state == ViewPager.SCROLL_STATE_IDLE && chageImage)
{
viewPager.setCurrentItem(setPos, false);
}
}
});
// display the 1st item as current item
viewPager.setCurrentItem(1);
}
// pager adapter
public class ViewPagerAdapter extends FragmentStatePagerAdapter
{
public ViewPagerAdapter(FragmentManager fm)
{
super(fm);
}
@Override
public Fragment getItem(int position)
{
PagerFragment fragment = new PagerFragment();
Bundle bundle = new Bundle();
bundle.putString("pos", str.get(position));
fragment.setArguments(bundle);
return fragment;
}
@Override
public int getCount()
{
return str.size();
}
}
// fragment to display in adapter
public class PagerFragment extends Fragment
{
Bundle bundle;
String pos;
public PagerFragment()
{
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
bundle = getArguments();
pos = bundle.getString("pos");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
TextView textView = new TextView(ViewPagerCircular_new.this);
textView.setGravity(Gravity.CENTER);
textView.setTextSize(25);
textView.setText(pos);
return textView;
}
}
}
Solution 4
Try this
((ViewPager) container)
.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
Log.i("TAG", "pos::" + position);
}
@Override
public void onPageScrollStateChanged(int state) {
// TODO Auto-generated method stub
int currentPage = pager.getCurrentItem();
Log.i("TAG", "currentPage::" + currentPage);
Log.i("TAG", "currentState::" + currentState);
Log.i("TAG", "previousState::" + previousState);
if (currentPage == 4 || currentPage == 0) {
previousState = currentState;
currentState = state;
if (previousState == 1 && currentState == 0) {
pager.setCurrentItem(currentPage == 0 ? 4 : 0);
}
}
}
@Override
public void onPageScrolled(int arg0, float arg1,
int arg2) {
// TODO Auto-generated method stub
}
});
return
This should be placed inside
@Override
public Object instantiateItem(final View container, int position) {}
Solution 5
I slightly modified the answer from @portfoliobuilder. It's very simple.
Setting the current item with no smooth scroll until PageChangeState
to be "0", so it would be very smooth.
((ViewPager)container).setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
currentPage = position;
}
@Override
public void onPageScrollStateChanged(int state) {
// state equals to 0 means the scroll action is stop
if(state == 0) {
if(currentPage == 0)
((ViewPager)container).setCurrentItem(imageResourceList.size()-2,false);
if(currentPage == imageResourceList.size()-1)
((ViewPager)container).setCurrentItem(1,false);
}
}
});
portfoliobuilder
I am a mobile app developer. Hit me up if you have any questions: [email protected]
Updated on March 07, 2020Comments
-
portfoliobuilder about 4 years
I would like to set my ViewPager to do circular scrolling. I want the first page to be able to scroll to page 2 AND the last page. And I would like my last page to scroll to [last page -1] AND the first page. I have made an attempt, although I am not sure when to call the method I have created. There does not seem to be a method in ViewPager that handles this sort of thing, so I created the below.
public ViewPagerAdapter(final ViewPager pager, int... pageIDs) { super(); int actualNoOfIDs = pageIDs.length; count = actualNoOfIDs + 2; Log.d(TAG, "actualNoOfIDs: " + actualNoOfIDs + "count: " + count); pageIDsArray = new int[count]; for (int i = 0; i < actualNoOfIDs; i++) { pageIDsArray[ i + 1] = pageIDs[i]; } pageIDsArray[0] = pageIDs[actualNoOfIDs - 1]; pageIDsArray[count - 1] = pageIDs[0]; Log.d(TAG, "actualNoOfIDs#2: " + actualNoOfIDs + "count#2: " + count); pager.setOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { int pageCount = getCount(); if (position == 0) { pager.setCurrentItem(pageCount - 2, false); } else if (position == pageCount - 1) { pager.setCurrentItem(1, false); } } @Override public void onPageScrollStateChanged(int state) { // TODO Auto-generated method stub Log.d(TAG, "onPageScrollStateChanged()"); // if (state == ViewPager.SCROLL_STATE_IDLE) { // int pageCount = getCount(); // int currentItem = pager.getCurrentItem(); // if (currentItem == 0) { // pager.setCurrentItem(pageCount - 2, false); // } else if (currentItem == pageCount - 1) { // pager.setCurrentItem(1, false); // } // } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // TODO Auto-generated method stub Log.d(TAG, "onPageScrolled()"); } }); }
My entire code is sort of lengthy, but if it helps I can post it.
public class ViewPagerAdapter extends PagerAdapter { public static String TAG = ViewPagerAdapter.class.getSimpleName(); private int count; private int[] pageIDsArray; private TextToSpeech btnTTS; private TtsButton tTSBtn; String inputTxt; Context context; View itemView; TextView tvNumber; // container for atomic number TextView tvSymbol; // container for symbol TextView tvWeight; // container for weight TextView tvName; // container for name TextView tvGroup; // container for group TextView tvPeriod; // container for period TextView tvBlock; // container for block TextView tvFamily; // container for family TextView tvColor; // container for color TextView tvPhase; // container for phase TextView tvMelt; // container for melting point TextView tvBoil; // container for boiling point TextView tvNeutrons; // container for neutrons TextView tvProtons; // container for protons TextView tvElectrons; // container for electrons TextView tvUrl; // container for electrons public ViewPagerAdapter(Context context, List<Integer> arrayAtomicNum, List<String> arrayName, List<String> arraySymbol, List<String> arrayFamily, List<String> arrayPhase, List<String> arrayColor, List<Integer> arrayGroup, List<Integer> arrayPeriod, List<String> arrayBlock, List<Integer> arrayProtons, List<Integer> arrayNeutrons, List<Integer> arrayElectrons, List<Double> arrayWeight, List<Double> arrayMelt, List<Double> arrayBoil, List<String> arrayUrl) { this.context = context; ElementStructure.arrayAtomicNum = arrayAtomicNum; ElementStructure.arrayName = arrayName; ElementStructure.arraySymbol = arraySymbol; ElementStructure.arrayFamily = arrayFamily; ElementStructure.arrayPhase = arrayPhase; ElementStructure.arrayColor = arrayColor; ElementStructure.arrayGroup = arrayGroup; ElementStructure.arrayPeriod = arrayPeriod; ElementStructure.arrayBlock = arrayBlock; ElementStructure.arrayProtons = arrayProtons; ElementStructure.arrayNeutrons = arrayNeutrons; ElementStructure.arrayElectrons = arrayElectrons; ElementStructure.arrayWeight = arrayWeight; ElementStructure.arrayMelt = arrayMelt; ElementStructure.arrayBoil = arrayBoil; ElementStructure.arrayUrl = arrayUrl; } public ViewPagerAdapter(final ViewPager pager, int... pageIDs) { super(); int actualNoOfIDs = pageIDs.length; count = actualNoOfIDs + 2; Log.d(TAG, "actualNoOfIDs: " + actualNoOfIDs + "count: " + count); pageIDsArray = new int[count]; for (int i = 0; i < actualNoOfIDs; i++) { pageIDsArray[ i + 1] = pageIDs[i]; } pageIDsArray[0] = pageIDs[actualNoOfIDs - 1]; pageIDsArray[count - 1] = pageIDs[0]; Log.d(TAG, "actualNoOfIDs#2: " + actualNoOfIDs + "count#2: " + count); pager.setOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { int pageCount = getCount(); if (position == 0) { pager.setCurrentItem(pageCount - 2, false); } else if (position == pageCount - 1) { pager.setCurrentItem(1, false); } } @Override public void onPageScrollStateChanged(int state) { // TODO Auto-generated method stub Log.d(TAG, "onPageScrollStateChanged()"); // if (state == ViewPager.SCROLL_STATE_IDLE) { // int pageCount = getCount(); // int currentItem = pager.getCurrentItem(); // if (currentItem == 0) { // pager.setCurrentItem(pageCount - 2, false); // } else if (currentItem == pageCount - 1) { // pager.setCurrentItem(1, false); // } // } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // TODO Auto-generated method stub Log.d(TAG, "onPageScrolled()"); } }); } @Override public int getCount() { // TODO Auto-generated method stub return ElementStructure.arrayAtomicNum.size(); } @Override public boolean isViewFromObject(View view, Object object) { // TODO Auto-generated method stub return view == ((RelativeLayout) object); } @Override public Object instantiateItem(ViewGroup container, final int position) { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); int layoutColorId = ElementStructure.arrayLayoutColor.get(position); if (layoutColorId == 1) { itemView = inflater.inflate(R.layout.frame_learn_a, container, false); } else if (layoutColorId == 2) { itemView = inflater.inflate(R.layout.frame_learn_b, container, false); } else if (layoutColorId == 3) { itemView = inflater.inflate(R.layout.frame_learn_c, container, false); } else if (layoutColorId == 4) { itemView = inflater.inflate(R.layout.frame_learn_d, container, false); } Button btnSpeak = (Button)itemView.findViewById(R.id.btnaudio); btnSpeak.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub inputTxt = ElementStructure.arrayName.get(position); tTSBtn = new TtsButton(this, inputTxt); } }); // atomic number textView tvNumber = (TextView)itemView.findViewById(R.id.metanumber); // symbol textView tvSymbol = (TextView)itemView.findViewById(R.id.metasymbol); // weight textView tvWeight = (TextView)itemView.findViewById(R.id.metaweight); // name textView tvName = (TextView)itemView.findViewById(R.id.metaname); // group textView tvGroup = (TextView)itemView.findViewById(R.id.metagroup); // period textView tvPeriod = (TextView)itemView.findViewById(R.id.metaperiod); // block textView tvBlock = (TextView)itemView.findViewById(R.id.metablock); // family textView tvFamily = (TextView)itemView.findViewById(R.id.metafamily); // color textView tvColor = (TextView)itemView.findViewById(R.id.metacolor); // phase textView tvPhase = (TextView)itemView.findViewById(R.id.metaphase); // melting point textView tvMelt = (TextView)itemView.findViewById(R.id.metamelt); // boiling point textView tvBoil = (TextView)itemView.findViewById(R.id.metaboil); // neutrons textView tvNeutrons = (TextView)itemView.findViewById(R.id.metaneutrons); // protons textView tvProtons = (TextView)itemView.findViewById(R.id.metaprotons); // electrons textView tvElectrons = (TextView)itemView.findViewById(R.id.metaelectrons); // url textView tvUrl = (TextView)itemView.findViewById(R.id.metaurl); // capture position and set to the TextViews tvNumber.setText(String.valueOf(ElementStructure.arrayAtomicNum.get(position))); tvSymbol.setText(ElementStructure.arraySymbol.get(position)); tvWeight.setText(String.valueOf(ElementStructure.arrayWeight.get(position))); tvName.setText(ElementStructure.arrayName.get(position)); tvPeriod.setText(String.valueOf(ElementStructure.arrayPeriod.get(position))); tvBlock.setText(String.valueOf(ElementStructure.arrayBlock.get(position))); tvFamily.setText(ElementStructure.arrayFamily.get(position)); tvColor.setText(ElementStructure.arrayColor.get(position)); tvPhase.setText(ElementStructure.arrayPhase.get(position)); tvNeutrons.setText(String.valueOf(ElementStructure.arrayNeutrons.get(position))); tvProtons.setText(String.valueOf(ElementStructure.arrayProtons.get(position))); tvElectrons.setText(String.valueOf(ElementStructure.arrayElectrons.get(position))); tvUrl.setText(ElementStructure.arrayUrl.get(position)); // capture position, adjust for 0 value cases if (ElementStructure.arrayGroup.get(position) == 0) { tvGroup.setText("n/a"); } else { tvGroup.setText(String.valueOf(ElementStructure.arrayGroup.get(position))); } if (ElementStructure.arrayMelt.get(position) == 0) { tvMelt.setText("n/a"); } else { tvMelt.setText(String.valueOf(ElementStructure.arrayMelt.get(position))); } if (ElementStructure.arrayBoil.get(position) == 0) { tvBoil.setText("n/a"); } else { tvBoil.setText(String.valueOf(ElementStructure.arrayBoil.get(position))); } // add fragments to container (ViewPager) ((ViewPager) container).addView(itemView); return itemView; } @Override public void destroyItem(ViewGroup container, int position, Object object) { Log.d(TAG, "destroyItem()"); // remove fragments from container (ViewPager) ((ViewPager) container).removeView((RelativeLayout) object); } @Override public void finishUpdate(View container) { // TODO Auto-generated method stub Log.d(TAG, "finishUpdate()"); } @Override public void restoreState(Parcelable state, ClassLoader loader) { // TODO Auto-generated method stub Log.d(TAG, "restoreState()"); } @Override public Parcelable saveState() { // TODO Auto-generated method stub Log.d(TAG, "saveState()"); return null; } @Override public void startUpdate(View container) { // TODO Auto-generated method stub Log.d(TAG, "startUpdate()"); } public class TtsButton extends Activity implements OnInitListener { public TtsButton(OnClickListener onClickListener, String inputTxt) { super(); tTSCheck(inputTxt); } private void tTSCheck (String inputTxt) { int resultCodeCheck = TextToSpeech.Engine.CHECK_VOICE_DATA_PASS; if (resultCodeCheck == 1) { btnTTS = new TextToSpeech(context, this); } else { Intent installTTSFiles = new Intent(); //missing data, install it installTTSFiles.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); startActivity(installTTSFiles); } } @Override public void onInit(int status) { // TODO Auto-generated method stub if(status == TextToSpeech.SUCCESS) { Log.i(TAG, "TTS INIT: SUCCESS"); btnTTS.setLanguage(Locale.US); btnTTS.speak(inputTxt, TextToSpeech.QUEUE_FLUSH, null); } else if(status == TextToSpeech.ERROR) { Log.e(TAG, "TTS INIT: ERROR"); } } @Override public void onPause() { if (btnTTS != null) { btnTTS.stop(); btnTTS.shutdown(); } super.onPause(); } } //end embedded class } //end ViewPagerAdapter
Thanks in advance. I was thinking about trying to use motion gesture, however, I really do not understand why my method won't work so long that I can call it in the correct spot. My initial thought was to call it in "Object instantiateItem(ViewGroup container, final int position)" every time the position changes, and if the conditions were correct. But I'm not sure if that is best.
-
Jayant Arora over 9 yearsYou cannot maintain the swipe animation if going right to left on swiping from laspage to 1st age, 1st page appears without any animation and vice-verse, moreover if u enable smooth scrolling, int his code it jumps back and forth to 1st and last page in infiniteloop--> mViewPager.setCurrentItem(0, true);
-
tobi_b over 9 yearsI've improved the solution above and created a little library on github. Feel free to check it out :) github.com/TobiasBuchholz/CircularViewPager
-
Rohan Kandwal about 9 yearsWorking perfectly. Since I am using CirclePageIndicator, I couldn't add fake pages at the start and end. Using this code, I can easily go circulate my ViewPager.
-
Ravi almost 9 yearsPerfect , but how to make scrolling smooth ?
-
Stoycho Andreev over 8 years@tobi_b ViewPager class method setOnPageChangeListener(listener) is deprecated so you have to use addOnPageChangeListener(listener). For more details check this stackoverflow.com/questions/30867852/…
-
Mehvish Ali over 7 yearswhen i swipe to left on the first item i get empty page
-
Mehvish Ali over 7 years
setCurrentItem(1, true)
for smooth scrolling -
Daksh Gargas almost 7 yearsHi! I tried your answer and turns out if you'll enable smooth scrolling in
mViewPager.setCurrentItem(0, true);
Then it will keep on scrolling back and forth. -
BMM over 6 years@RohanKandwal can u paste your working code here..because in my case ,the solution is not working.
-
petrumo over 6 yearsHi, I have fixed the smooth scrolling by doing next with this checks: if (state == ViewPager.SCROLL_STATE_IDLE && mScrollState == ViewPager.SCROLL_STATE_DRAGGING)
-
CoolMind over 6 yearsThanks, but a scrolling is not smooth, tips above didn't help.
-
TacB0sS almost 6 yearsThe flickering just looks terrible!
-
Shailendra Madda over 5 years@petrumo Thanks for your comment it helped me to stop the infinite loop when we
set smooth scrolling
totrue
-
Serg Burlaka almost 5 years@tobi_b i found your lib It is simply and cooll work. Thanks a lot for great job!
-
tm1701 over 4 yearsNice solution! The problem is that I first see very briefly the fake page and immediately afterwards I see the right page. I use AndroidX. How can this be solved?
-
Atul Bhardwaj about 4 yearsI have edited above code. Now it will have smooth scrolling for last and first item too
-
Mustafa Demir over 2 yearsDon't you need to call
super
in onPageScrolled function?