修复圆形ViewPager的动画

Col*_*tin 15 android android-viewpager

目标

构建一个循环ViewPager.

第一个元素允许您达到峰值到最后一个元素并向其滑动,反之亦然.您应该能够永远向任一方向滑动.

现在这已经完成,但这些问题对我的实现不起作用.以下是一些供参考:

我是如何尝试解决问题的

我们将使用大小为7的数组作为示例.要素如下:

[0][1][2][3][4][5][6]
Run Code Online (Sandbox Code Playgroud)

当你在元素0时,ViewPagers不允许你向左滑动!多么可怕:(.为了解决这个问题,我在正面和结尾添加了1个元素.

   [0][1][2][3][4][5][6]      // Original
[0][1][2][3][4][5][6][7][8]   // New mapping
Run Code Online (Sandbox Code Playgroud)

当ViewPageAdapter请求(instantiateItem())元素0时,我们返回元素7.当ViewPageAdapter请求元素8时,我们返回元素1.

同样在ViewPager的OnPageChangeListener中,当使用0调用onPageSelected时,我们使用setCurrentItem(7),当使用8调用onPageSelected时,我们使用setCurrentItem(1).

这有效.

问题

当您从1向左滑动到0并且我们设置了CurrentItem(7)时,它将一直向右移动6个全屏幕.这并没有给出圆形ViewPager的外观,它给出了冲动到用户请求滑动动作的相反方向的最后一个元素的外观!

这非常非常刺耳.

我是如何试图解决这个问题的

我的第一个倾向是关闭平滑(即所有)动画.它有点好,但是当你从最后一个元素移动到第一个元素时,它现在变得不稳定,反之亦然.

然后我制作了自己的Scroller.

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

我发现在元素之间移动时始终有1次调用startScroll(),除非我从1移动到7和7移动到1.

第一个调用是方向和数量的正确动画.

第二个调用是将所有内容向右移动多个页面的动画.

这是事情变得非常棘手的地方.

我认为解决方案是跳过第二个动画.所以我做了.会发生一个平滑的动画,从1到7,有0个打嗝.完善!但是,如果你滑动,甚至点击屏幕,你会突然(没有动画)在元素6!如果你从7滑动到1,你实际上是在元素2.没有调用setCurrentItem(2)甚至调用OnPageChangeListener,表明你在任何时间点到达2.

但是你实际上并不是第2元,这有点好.您仍处于元素1,但将显示元素2的视图.然后当你向左滑动时,你会转到元素1.即使你已经真的在元素1上了......一些代码如何帮助清理:

动画被打破,但没有奇怪的副作用

@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    super.startScroll(startX, startY, dx, dy, duration);
}
Run Code Online (Sandbox Code Playgroud)

动画有效!但一切都很奇怪和可怕......

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

唯一的区别是,当第二个动画(大于480像素屏幕的宽度)被调用时,我们忽略它.

在阅读了Scroller的Android源代码后,我发现startScroll没有开始滚动任何内容.它设置要滚动的所有数据,但不启动任何内容.

我的预感

当您执行循环操作(1到7或7到1)时,有两个调用startScroll().我认为这两个电话之间的某些问题导致了一个问题.

  1. 用户从元素1滚动到元素7,导致从0跳转到7.这应该是左边的动画.
  2. 调用startScroll()指示左侧的短动画.
  3. 我觉得这可能让我感到羞耻
  4. 调用startScroll()指示右侧动画.
  5. 出现了向右的长动画.

如果我评论4,那么5变成"左边的短正确动画,事情变得疯狂"

摘要

我对循环ViewPager的实现有效,但动画被破坏了.在尝试修复动画时,它会破坏ViewPager的功能.我正在旋转我的车轮试图找出如何使它工作.帮我!:)

如果有什么不清楚请在下面评论,我会澄清.我意识到我对事情的破坏并不十分精确.这很难描述,因为它甚至不清楚我在屏幕上看到了什么.如果我的解释是一个问题我可以解决它,请告诉我!

干杯,科廷

稍微修改此代码以使其本身更具可读性,但功能与我当前的代码迭代相同.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

CircularViewPageAdapter.getCount

@Override
public int getCount() {
    return m_Length + 2;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Jor*_*lar 1

我认为最好的可行方法是不使用普通列表来对列表进行包装,当执行get(pos)方法来获取创建视图的对象时,您可以制作类似这样的get(pos % numberOfViews ),当它询问列表的大小时,您输入列表为Integer.MAX_VALUE并在其中开始列表,这样您就可以说这几乎不可能出现错误,除非它们实际上刷到相同的位置侧,直到到达列表末尾。如果时间允许的话,我会尝试稍后发布一个概念验证。

编辑:

我已经尝试过这段代码,我知道每个视图上都会显示一个简单的文本框,但事实是它工作得很好,它可能会更慢,具体取决于视图总数,但概念证明就在这里。我所做的是 MAX_NUMBER_VIEWS代表用户在停止之前可以完全给出的最大次数。正如你所看到的,我以数组的长度启动了viewpager,这将是它第二次出现,这样你就可以向左和向右多转一圈,但你可以根据需要更改它。我希望我不会因为一个实际上有效的解决方案而得到更多的负面评价。

活动:

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);
Run Code Online (Sandbox Code Playgroud)

适配器:

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

}
Run Code Online (Sandbox Code Playgroud)