Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 8304525
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: June 8, 20262026-06-08T17:52:16+00:00 2026-06-08T17:52:16+00:00

Goal Build a Circular ViewPager. The first element lets you peak to the last

  • 0

Goal

Build a Circular ViewPager.

The first element lets you peak to the last element and swipe to it, and vice versa. You should be able to swipe in either direction forever.

Now this has been accomplished before, but these questions do not work for my implementation. Here are a few for reference:

  • how to create circular viewpager?
  • ViewPager as a circular queue / wrapping
  • https://github.com/antonyt/InfiniteViewPager

How I Tried to Solve the Problem

We will use an array of size 7 as an example. The elements are as follows:

[0][1][2][3][4][5][6]

When you are at element 0, ViewPagers do not let you swipe left! How terrible :(. To get around this, I added 1 element to the front and end.

   [0][1][2][3][4][5][6]      // Original
[0][1][2][3][4][5][6][7][8]   // New mapping

When the ViewPageAdapter asks for (instantiateItem()) element 0, we return element 7. When the ViewPageAdapter asks for element 8 we return element 1.

Likewise in the OnPageChangeListener in the ViewPager, when the onPageSelected is called with 0, we setCurrentItem(7), and when it’s called with 8 we setCurrentItem(1).

This works.

The Problem

When you swipe to the left from 1 to 0, and we setCurrentItem(7), it will animate all the way to right by 6 full screens. This doesn’t give the appearance of a circular ViewPager, it gives the appearence rushing to the last element in the opposite direction the user requested with their swipe motion!

This is very very jarring.

How I Tried to Solve This

My first inclination was to turn off smooth (ie, all) animations. It’s a bit better, but it’s now choppy when you move from the last element to the first and vice versa.

I then made my own Scroller.

http://developer.android.com/reference/android/widget/Scroller.html

What I found was that there is always 1 call to startScroll() when moving between elements, except when I move from 1 to 7 and 7 to 1.

The first call is the correct animation in direction and amount.

The second call is the animation that moves everything to the right by multiple pages.

This is where things got really tricky.

I thought the solution was to just skip the second animation. So I did. What happens is a smooth animation from 1 to 7 with 0 hiccups. Perfect! However, if you swipe, or even tap the screen, you are suddenly (with no animation) at element 6! If you had swiped from 7 to 1, you’ll actually be at element 2. There is no call to setCurrentItem(2) or even a call to the OnPageChangeListener indicating that you arrived at 2 at any point in time.

But you’re not actually at element 2, which is kind of good. You are still at element 1, but the view for element 2 will be shown. And then when you swipe to the left, you go to element 1. Even though you were really at element 1 already.. How about some code to help clear things up:

Animation is broken, but no weird side effects

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

Animation works! But everything is strange and scary…

@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    if (dx > 480 || dx < -480) {
    } else {
        super.startScroll(startX, startY, dx, dy, duration);
    }
}

The ONLY difference is that when the second animation (bigger than the width of the 480 pixel screen) is called, we ignore it.

After reading through the Android Source code for Scroller, I found that startScroll does not start scrolling anything. It sets up all the data to be scrolled, but doesn’t initiate anything.

My Hunch

When you do the circular action (1 to 7 or 7 to 1), there are two calls to startScroll(). I think something in between the two calls is causing an issue.

  1. User scrolls from element 1 to element 7 causing a jump from 0 to 7. This should animate to the left.
  2. startScroll() is called indicating a short animation to the left.
  3. STUFF HAPPENS THAT MAKES ME CRY PROBABLY I THINK
  4. startScroll() is called indicating a long animation to the right.
  5. Long animation to the right occurs.

If I comment out 4, then 5 becomes “Short correct animation to the left, things go crazy”

Summary

My implementation of a Circular ViewPager works, but the animation is broken. Upon trying to fix the animation, it breaks the functionality of the ViewPager. I am currently spinning my wheels trying to figure out how to make it work. Help me! 🙂

If anything is unclear please comment below and I will clarify. I realize I was not very precise with how things are broken. It’s difficult to describe because it’s not even clear what I’m seeing on the screen. If my explanation is an issue I can work on it, let me know!

Cheers,
Coltin

Code

This code is slightly modified to make it more readable on its own, though the functionality is identical to my current iteration of the code.

OnPageChangeListener.onPageSelected

@Override
public void onPageSelected(int _position) {
    boolean animate = true;
    if (_position < 1) {
        // Swiping left past the first element, go to element (9 - 2)=7
        setCurrentItem(getAdapter().getCount() - 2, animate);
    } else if (_position >= getAdapter().getCount() - 1) {
        // Swiping right past the last element
        setCurrentItem(1, animate);
    }
}

CircularScroller.startScroll

@Override
public void startScroll(int _startX, int _startY, int _dx, int _dy, int _duration) {
    // 480 is the width of the screen
    if (dx > 480 || dx < -480) {
        // Doing nothing in this block shows the correct animation,
        // but it causes the issues mentioned above

        // Uncomment to do the big scroll!
        // super.startScroll(_startX, _startY, _dx, _dy, _duration);

        // lastDX was to attempt to reset the scroll to be the previous
        // correct scroll distance; it had no effect
        // super.startScroll(_startX, _startY, lastDx, _dy, _duration);
    } else {
        lastDx = _dx;
        super.startScroll(_startX, _startY, _dx, _dy, _duration);
    }
}

CircularViewPageAdapter.CircularViewPageAdapter

private static final int m_Length = 7; // For our example only
private static Context m_Context;
private boolean[] created = null; // Not the best practice..

public CircularViewPageAdapter(Context _context) {
    m_Context = _context;
    created = new boolean[m_Length];
    for (int i = 0; i < m_Length; i++) {
        // So that we do not create things multiple times
        // I thought this was causing my issues, but it was not
        created[i] = false;
    }
}

CircularViewPageAdapter.getCount

@Override
public int getCount() {
    return m_Length + 2;
}

CircularViewPageAdapter.instantiateItem

@Override
public Object instantiateItem(View _collection, int _position) {

    int virtualPosition = getVirtualPosition(_position);
    if (created[virtualPosition - 1]) {
        return null;
    }

    TextView tv = new TextView(m_Context);
    // The first view is element 1 with label 0! :)
    tv.setText("Bonjour, merci! " + (virtualPosition - 1));
    tv.setTextColor(Color.WHITE);
    tv.setTextSize(30);

    ((ViewPager) _collection).addView(tv, 0);

    return tv;
}

CircularViewPageAdapter.destroyItem

@Override
public void destroyItem(ViewGroup container, int position, Object view) {
    ViewPager viewPager = (ViewPager) container;
    // If the virtual distance is distance 2 away, it should be destroyed.
    // If it's not intuitive why this is the case, please comment below
    // and I will clarify
    int virtualDistance = getVirtualDistance(viewPager.getCurrentItem(), getVirtualPosition(position));
    if ((virtualDistance == 2) || ((m_Length - virtualDistance) == 2)) {
        ((ViewPager) container).removeView((View) view);
        created[getVirtualPosition(position) - 1] = false;
    }
}
  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-06-08T17:52:18+00:00Added an answer on June 8, 2026 at 5:52 pm

    I think the best doable approach would be instead of using a normal list to have a wrapper to the List that when the get(pos) method is executed to obtain the object to create the view, you make something like this get(pos % numberOfViews) and when it ask for the size of the List you put that the List is Integer.MAX_VALUE and you start your List in the middle of it so you can say that is mostly impossible to have an error, unless they actually swipe to the same side until the reach the end of the List. I will try to post a proof of concept later this weak if the time allows me to do so.

    EDIT:

    I have tried this piece of code, i know is a simple textbox shown on each view, but the fact is that it works perfectly, it might be slower depending on the total amount of views but the proof of concept is here. What i have done is that the MAX_NUMBER_VIEWS represents what is the maximum numbers of times a user can completely give before he is stopped. and as you can see i started the viewpager at the length of my array so that would be the second time it appears so you have one turn extra to the left and right but you can change it as you need it. I hope i do not get more negative points for a solution that in fact does work.

    ACTIVITY:

    pager = (ViewPager)findViewById(R.id.viewpager);
    String[] articles = {"ARTICLE 1","ARTICLE 2","ARTICLE 3","ARTICLE 4"};
    pager.setAdapter(new ViewPagerAdapter(this, articles));
    pager.setCurrentItem(articles.length);
    

    ADAPTER:

    public class ViewPagerAdapter extends PagerAdapter {
    
    private Context ctx;
    private String[] articles;
    private final int MAX_NUMBER_VIEWS = 3;
    
    public ViewPagerAdapter(Context ctx, String[] articles) {
        this.ctx = ctx;
        this.articles = articles.clone();
    }
    
    @Override
    public int getCount() {
        return articles.length * this.MAX_NUMBER_VIEWS;
    }
    
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        TextView view = new TextView(ctx);
        view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                                              LayoutParams.MATCH_PARENT));
        int realPosition = position % articles.length;
        view.setText(this.articles[realPosition]);
        ((ViewPager) container).addView(view);
        return view;
    }
    
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        ((ViewPager) container).removeView((View) object);
    }
    
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == ((View) object);
    }
    
    @Override
    public Parcelable saveState() {
        return null;
    }
    
    }
    
    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

My goal is to build a simple custom guard with Guard . The gem
My goal is to build an engine that takes the latest HL7 3.0 CDA
Okay, so my goal is to build a easy to use protocol for sending
For homework, I need to build a small java application.The main goal is to
In the Maven document Introduction to the Build Lifecycle , a goal of display:time
Maven newbie question :) For 'war' packaging, war:war default goal run in 'package' build
Ok guys, today's goal is to build a Turing machine simulator. For those that
Goal: Build an Android app that discovers the names and addresses of BT devices
My goal is to build a system where each user has an email address
I'm having trouble getting Code Analysis to run on the build server. My goal

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.