title: Fix the Animation of a Circular ViewPager
body: <p><strong>Goal</strong></p>
  
  <p>Build a Circular ViewPager.</p>
  
  <p>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.</p>
  
  <p>Now this has been accomplished before, but these questions do not work for my implementation. Here are a few for reference:</p>
  
  <ul>
  <li><a href="https://stackoverflow.com/questions/11465815/how-to-create-circular-viewpager">how to create circular viewpager?</a></li>
  <li><a href="https://stackoverflow.com/questions/7546224/viewpager-as-a-circular-queue-wrapping">ViewPager as a circular queue / wrapping</a></li>
  <li><a href="https://github.com/antonyt/InfiniteViewPager" rel="nofollow noreferrer">https://github.com/antonyt/InfiniteViewPager</a></li>
  </ul>
  
  <p><strong>How I Tried to Solve the Problem</strong></p>
  
  <p>We will use an array of size 7 as an example. The elements are as follows:</p>
  
  <pre><code>[0][1][2][3][4][5][6]
  </code></pre>
  
  <p>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.</p>
  
  <pre><code>   [0][1][2][3][4][5][6]      // Original
  [0][1][2][3][4][5][6][7][8]   // New mapping
  </code></pre>
  
  <p>When the ViewPageAdapter asks for (instantiateItem()) element 0, we return element 7. When the ViewPageAdapter asks for element 8 we return element 1.</p>
  
  <p>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).</p>
  
  <p>This works.</p>
  
  <p><strong>The Problem</strong></p>
  
  <p>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!</p>
  
  <p>This is very very jarring.</p>
  
  <p><strong>How I Tried to Solve This</strong></p>
  
  <p>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.</p>
  
  <p>I then made my own Scroller.</p>
  
  <p><a href="http://developer.android.com/reference/android/widget/Scroller.html" rel="nofollow noreferrer">http://developer.android.com/reference/android/widget/Scroller.html</a></p>
  
  <p>What I found was that there is always 1 call to startScroll() when moving between elements, <strong>except</strong> when I move from 1 to 7 and 7 to 1.</p>
  
  <p>The first call is the correct animation in direction and amount.</p>
  
  <p>The second call is the animation that moves everything to the right by multiple pages.</p>
  
  <p>This is where things got really tricky.</p>
  
  <p>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.</p>
  
  <p>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:</p>
  
  <p><strong>Animation is broken, but no weird side effects</strong></p>
  
  <pre><code>@Override
  public void startScroll(int startX, int startY, int dx, int dy, int duration) {
      super.startScroll(startX, startY, dx, dy, duration);
  }
  </code></pre>
  
  <p><strong>Animation works! But everything is strange and scary...</strong></p>
  
  <pre><code>@Override
  public void startScroll(int startX, int startY, int dx, int dy, int duration) {
      if (dx &gt; 480 || dx &lt; -480) {
      } else {
          super.startScroll(startX, startY, dx, dy, duration);
      }
  }
  </code></pre>
  
  <p>The ONLY difference is that when the second animation (bigger than the width of the 480 pixel screen) is called, we ignore it.</p>
  
  <p>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.</p>
  
  <p><strong>My Hunch</strong></p>
  
  <p>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.</p>
  
  <ol>
  <li>User scrolls from element 1 to element 7 causing a jump from 0 to 7. This should animate to the left.</li>
  <li>startScroll() is called indicating a short animation to the left.</li>
  <li><strong>STUFF HAPPENS THAT MAKES ME CRY PROBABLY I THINK</strong></li>
  <li>startScroll() is called indicating a <strong>long</strong> animation to the <strong>right</strong>.</li>
  <li>Long animation to the right occurs.</li>
  </ol>
  
  <p>If I comment out 4, then 5 becomes "Short correct animation to the left, things go crazy"</p>
  
  <p><strong>Summary</strong></p>
  
  <p>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! :)</p>
  
  <p>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!</p>
  
  <p>Cheers,
  Coltin</p>
  
  <p><strong>Code</strong></p>
  
  <p>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.</p>
  
  <p><em>OnPageChangeListener.onPageSelected</em></p>
  
  <pre><code>@Override
  public void onPageSelected(int _position) {
      boolean animate = true;
      if (_position &lt; 1) {
          // Swiping left past the first element, go to element (9 - 2)=7
          setCurrentItem(getAdapter().getCount() - 2, animate);
      } else if (_position &gt;= getAdapter().getCount() - 1) {
          // Swiping right past the last element
          setCurrentItem(1, animate);
      }
  }
  </code></pre>
  
  <p><em>CircularScroller.startScroll</em></p>
  
  <pre><code>@Override
  public void startScroll(int _startX, int _startY, int _dx, int _dy, int _duration) {
      // 480 is the width of the screen
      if (dx &gt; 480 || dx &lt; -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);
      }
  }
  </code></pre>
  
  <p><em>CircularViewPageAdapter.CircularViewPageAdapter</em></p>
  
  <pre><code>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 &lt; 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;
      }
  }
  </code></pre>
  
  <p><em>CircularViewPageAdapter.getCount</em></p>
  
  <pre><code>@Override
  public int getCount() {
      return m_Length + 2;
  }
  </code></pre>
  
  <p><em>CircularViewPageAdapter.instantiateItem</em></p>
  
  <pre><code>@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;
  }
  </code></pre>
  
  <p><em>CircularViewPageAdapter.destroyItem</em></p>
  
  <pre><code>@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;
      }
  }
  </code></pre>