【问题标题】:Fix the Animation of a Circular ViewPager修复圆形 ViewPager 的动画
【发布时间】:2013-08-14 21:03:50
【问题描述】:

目标

构建一个圆形 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 中,当 onPageSelected 用 0 调用时,我们设置CurrentItem(7),当它用 8 调用时,我们设置CurrentItem(1)。

这行得通。

问题

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

这是非常非常不和谐的。

我是如何解决这个问题的

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

然后我制作了自己的 Scroller。

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

我发现在元素之间移动时总是有 1 次 startScroll() 调用,except 当我从 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() 表示long动画到right
  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;
    }
}

【问题讨论】:

  • "当您位于元素 0 时,ViewPagers 不允许您向左滑动!多么可怕:(" - 您可以通过手动将当前页面设置为大于 0(即 1000)之前的值来解决此问题它显示给用户?然后他们可以在撞到“墙”之前向左或向右滚动很长时间。我曾在某个点使用过无限 ListView 的这种技术。但从来没有 ViewPager
  • 如果您进一步阅读,这正是我所做的。不再假设 0 必须是第 0 个元素,而是现在是跳转到“最后一个”元素的触发器。我在结尾做了类似的事情,真正的最后一个元素是跳转到真正元素 1 的“第一个”的触发器。
  • 也许你会感兴趣。正如你在 youtube 上看到的,我对动画没有任何问题 :) stackoverflow.com/a/32980681/1052261

标签: android android-viewpager


【解决方案1】:

我认为最好的可行方法是,当执行 get(pos) 方法以获取创建视图的对象时,而不是使用普通列表来对 List 进行包装,你做了这样的事情 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);

适配器:

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

}

【讨论】:

  • 如果可行,这将是一个非常简单的解决方案!这个周末我会试试看它是否有效。谢谢@Jorge! :)
  • 欢迎@Coltin,如果它有效或无效,请发布它,让其他人知道这是不是这样。祝你好运。
  • @Jorge Aguilar: 'populate()' 和 'addNewItem()' 是私有方法,所以它们不能被覆盖(除非修改原始 ViewPager 源代码)。无论如何-如果您可以发布代码演示您的解决方案,我会很高兴。这将非常有用。提前致谢
  • 这也是我的第一个想法。然而,在这样做之后,ViewPager 的初始化速度变得非常慢。似乎 getPageWidh() 被多次调用(跳过/虚拟)页面。这就是为什么我最终在这个线程中寻找更好的解决方案
  • @r-hold 只是为了告诉你这个概念证明我在我自己的应用程序中付诸实践,具有完整的布局和大约不到 100 个真实视图加上 2 个副本,它运行良好。有任何问题请告诉我。
猜你喜欢
  • 2014-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多