问题描述
目标
构建一个圆形 ViewPager.
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:
- 如何创建圆形viewpager?
- ViewPager 作为循环队列/包装
- https://github.com/antonyt/InfiniteViewPager
我是如何解决问题的
我们将使用一个大小为 7 的数组作为示例.要素如下:
We will use an array of size 7 as an example. The elements are as follows:
[0][1][2][3][4][5][6]
当您位于元素 0 时,ViewPagers 不允许您向左滑动!多么可怕:(.为了解决这个问题,我在前端和后端添加了 1 个元素.
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
当 ViewPageAdapter 请求 (instantiateItem()) 元素 0 时,我们返回元素 7.当 ViewPageAdapter 请求元素 8 时,我们返回元素 1.
When the ViewPageAdapter asks for (instantiateItem()) element 0, we return element 7. When the ViewPageAdapter asks for element 8 we return element 1.
同样在 ViewPager 的 OnPageChangeListener 中,当 onPageSelected 用 0 调用时,我们设置CurrentItem(7),当它用 8 调用时,我们设置CurrentItem(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).
这行得通.
问题
当你从 1 到 0 向左滑动时,我们设置了 CurrentItem(7),它将动画一直向右移动 6 个全屏.这并没有给出圆形 ViewPager 的外观,它给出的外观是按照用户通过滑动动作请求的相反方向冲向最后一个元素!
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.
我是如何解决这个问题的
我的第一个想法是关闭平滑(即所有)动画.它稍微好一点,但是当您从最后一个元素移动到第一个元素时,它现在变得不稳定,反之亦然.
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.
然后我制作了自己的 Scroller.
I then made my own Scroller.
http://developer.android.com/reference/android/widget/Scroller.html
我发现在元素之间移动时总是有 1 次 startScroll() 调用,except 当我从 1 移动到 7 和从 7 移动到 1 时.
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.
我认为解决方案是跳过第二个动画.所以我做了.会发生从 1 到 7 的平滑动画,其中 0 个打嗝.完美的!然而,如果你滑动,甚至点击屏幕,你会突然(没有动画)在元素 6!如果您从 7 滑到 1,您实际上将位于元素 2.没有调用 setCurrentItem(2),甚至没有调用 OnPageChangeListener,表明您在任何时间点到达 2.
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.
但你实际上并不在元素 2,这很好.您仍在元素 1 处,但将显示元素 2 的视图.然后当你向左滑动时,你会转到元素 1.即使你真的已经在元素 1 了.. 一些代码来帮助清理事情怎么样:
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:
动画坏了,但没有奇怪的副作用
@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 像素屏幕的宽度)被调用时,我们忽略它.
The ONLY difference is that when the second animation (bigger than the width of the 480 pixel screen) is called, we ignore it.
在阅读了 Scroller 的 Android 源代码后,我发现 startScroll 并没有开始滚动任何东西.它设置所有要滚动的数据,但不启动任何操作.
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.
我的预感
当您执行循环动作(1 到 7 或 7 到 1)时,会调用两次 startScroll().我认为这两个电话之间存在问题.
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 滚动到元素 7,导致从 0 跳转到 7.这应该向左移动.
- 调用 startScroll() 来指示左侧的短动画.
- 发生的事情让我想哭
- startScroll() 被调用,表示 long 动画到 right.
- 出现向右的长动画.
如果我注释掉 4,那么 5 就变成了向左短的正确动画,事情变得疯狂"
If I comment out 4, then 5 becomes "Short correct animation to the left, things go crazy"
总结
我的 Circular ViewPager 实现有效,但动画坏了.在尝试修复动画时,它会破坏 ViewPager 的功能.我目前正在旋转我的轮子,试图弄清楚如何让它工作.帮我!:)
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!
干杯,科尔廷
代码
此代码稍作修改,使其本身更具可读性,但功能与我当前的代码迭代相同.
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;
}
}
推荐答案
我认为最好的可行方法是在 get(pos) 方法被执行来获取创建视图的对象,你做这样的事情 get(pos % numberOfViews) 并且当它询问你放置的 List 的大小时, List 是 Integer.MAX_VALUE 并且您在列表的中间开始您的列表,因此您可以说这几乎不可能出错,除非它们实际上滑动到同一侧直到到达列表的末尾.如果时间允许的话,我会尝试稍后发布概念证明.
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.
我已经尝试过这段代码,我知道每个视图上都会显示一个简单的文本框,但事实是它可以完美运行,它可能会根据视图总数而变慢,但概念证明就在这里.我所做的是 MAX_NUMBER_VIEWS 表示用户在停止之前可以完全给出的最大次数.如您所见,我在数组的长度处启动了viewpager,这将是它第二次出现,因此您可以向左和向右多转一圈,但您可以根据需要进行更改.我希望我不会因为实际上确实有效的解决方案而得到更多的负面评价.
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.
活动:
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;
}
}
这篇关于修复圆形 ViewPager 的动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!