Исправить анимацию Circular ViewPager
Цель
Создайте круглый 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.
Аналогично в OnPageChangeListener в ViewPager, когда onPageSelected вызывается с 0, мы устанавливаем CurrentItem (7), а при вызове с 8 мы устанавливаем CurrentItem (1).
Это работает.
Проблема
Когда вы проведите пальцем влево от 1 до 0, и мы установим CurrentItem (7), он будет анимировать весь путь вправо на 6 полных экранов. Это не приводит к появлению кругового ViewPager, это дает возможность появляться на последнем элементе в обратном направлении, запрошенном пользователем с их движением.
Это очень раздражает.
Как я пытался решить эту проблему
Моя первая наклонность состояла в том, чтобы отключить плавные (т.е. все) анимации. Это немного лучше, но теперь он изменчив, когда вы переходите от последнего элемента к первому и наоборот.
Затем я сделал свой собственный Скроллер.
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 пикселей), мы игнорируем ее.
Прочитав исходный код Android для Scroller, я обнаружил, что startScroll не запускает прокрутку. Он устанавливает все данные для прокрутки, но ничего не инициирует.
Мой дог
Когда вы выполняете круговое действие (от 1 до 7 или от 7 до 1), есть два вызова startScroll(). Я думаю, что что-то между двумя вызовами вызывает проблему.
- Пользователь прокручивает от элемента 1 к элементу 7, вызывая скачок от 0 до 7. Это должно анимировать слева.
- startScroll() вызывается с указанием короткой анимации слева.
- STUFF ПРОСМОТРЕТЬ, ЧТО МОЖЕТ БЫТЬ МЕНЯ КРИВОЙ, ЧТО Я ДУМАЮ.
- startScroll() называется символом long для справа.
- Появляется длинная анимация вправо.
Если я прокомментирую 4, то 5 станет "Короткими правильными анимациями слева, все сойдет с ума"
Резюме
Моя реализация Circular ViewPager работает, но анимация нарушена. Попытавшись исправить анимацию, она нарушает функциональность ViewPager. В настоящее время я вращаю свои колеса, пытаясь понять, как заставить его работать. Помоги мне!:)
Если что-то неясно, прокомментируйте ниже, и я уточню. Я понимаю, что не очень точен, как все сломалось. Это сложно описать, потому что даже не ясно, что я вижу на экране. Если мое объяснение является проблемой, я могу работать над этим, дайте мне знать!
Cheers,
Колтин
код
Этот код слегка изменен, чтобы сделать его более понятным для себя, хотя функциональность идентична моей текущей итерации кода.
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 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;
}
}
Ответы
Ответ 1
Я думаю, что лучшим выполнимым подходом было бы вместо обычного списка, чтобы иметь экземпляр в List, когда при выполнении метода get (pos), чтобы получить объект для создания представления, вы делаете что-то вроде этого get (pos% numberOfViews), и когда он запрашивает размер списка, который вы ставите, что List Integer.MAX_VALUE, и вы начинаете свой список в посредине, чтобы вы могли сказать, что в большинстве случаев невозможно получить ошибку, если только они не перейдут на ту же сторону, пока не достигнете конца списка. Я попытаюсь опубликовать доказательство концепции позже этого слабого, если время позволяет мне это сделать.
EDIT:
Я пробовал эту часть кода, я знаю, что это простое текстовое поле, отображаемое на каждом представлении, но факт в том, что он работает отлично, он может быть медленнее в зависимости от общего количества просмотров, но доказательство концепции здесь. Что я сделал, так это то, что 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);
ADAPTER:
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;
}
}