Исправить анимацию Circular ViewPager

Цель

Создайте круглый ViewPager.

Первый элемент позволяет вам пик до последнего элемента и прокручивать его, и наоборот. Вы должны иметь возможность провести в любом направлении навсегда.

Теперь это было сделано раньше, но эти вопросы не работают для моей реализации. Вот несколько ссылок:

  • Как создать круговой просмотрщик?
  • ViewPager как круговая очередь / упаковка
  • https://github.com/antonyt/InfiniteViewPager

Как я пытался решить проблему

Мы будем использовать массив размером 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. Пользователь прокручивает от элемента 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; } } 

Solutions Collecting From Web of "Исправить анимацию Circular ViewPager"

Я считаю, что лучшим выполнимым подходом было бы вместо обычного списка, чтобы иметь экземпляр в список, который, когда метод get (pos) выполняется для получения объекта для создания представления, вы делаете что-то вроде этого get (pos% numberOfViews ), И когда он запрашивает размер списка, вы указываете, что List является 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); 

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