15

目标

构建一个圆形 ViewPager。

第一个元素可让您到达最后一个元素并滑动到它,反之亦然。您应该能够永远向任一方向滑动。

现在这已经完成了,但是这些问题对我的实现不起作用。这里有几个供参考:

我是如何尝试解决问题的

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

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

当您位于元素 0 时,ViewPagers 不允许您向左滑动!多么可怕:(。为了解决这个问题,我在前端和后端添加了 1 个元素。

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

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

同样在 ViewPager 的 OnPageChangeListener 中,当使用 0 调用 onPageSelected 时,我们设置 CurrentItem(7),当使用 8 调用它时,我们设置 CurrentItem(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);
}

动画作品!但一切都是陌生而可怕的……

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

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

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

我的预感

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

  1. 用户从元素 1 滚动到元素 7,导致从 0 跳转到 7。这​​应该向左移动。
  2. 调用 startScroll() 来指示左侧的短动画。
  3. 发生的事情让我哭了可能我认为
  4. 调用 startScroll() 来指示向右的动画。
  5. 出现向右的长动画。

如果我注释掉 4,那么 5 就变成“左边的正确动画,事情变得疯狂”

概括

我的 Circular 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);
    }
}

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

1 回答 1

1

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

编辑:

我已经尝试过这段代码,我知道每个视图上都显示了一个简单的文本框,但事实是它可以完美运行,它可能会根据视图的总数而变慢,但概念证明就在这里。我所做的是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);

适配器:

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

}
于 2012-07-27T15:51:09.537 回答