Исправить анимацию 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;
}

}