Исправить анимацию кругового ViewPager

Goal

Постройте Круговой ViewPager.

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

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

how to create circular viewpager? ViewPager as a circular queue / wrapping https://github.com/antonyt/InfiniteViewPager

How I Tried to Solve the Problem

Мы будем использовать массив размером 7 в качестве примера. Элементы следующие:

[0][1][2][3][4][5][6]

Когда вы находитесь в элементе 0, ViewPager не позволяет вам провести пальцем влево! Как ужасно :(. Чтобы обойти это, я добавил 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).

Это работает.

The Problem

Когда вы проведете пальцем влево от 1 до 0, и мы установимCurrentItem (7), он будет анимирован полностью на 6 полных экранов. Это не создает видимости кругового ViewPager, оно придает внешний вид стремительному движению к последнему элементу в противоположном направлении, запрошенном пользователем при движении пальцем!

Это очень очень неприятно.

How I Tried to Solve This

Моим первым стремлением было отключить плавные (т.е. все) анимации. Это немного лучше, но теперь оно нестабильно, когда вы переходите от последнего элемента к первому и наоборот.

Затем я сделал свой собственный скроллер.

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. Как насчет кода, который поможет прояснить ситуацию:

Animation is broken, but no weird side effects

@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    super.startScroll(startX, startY, dx, dy, duration);
}

Animation works! But everything is strange and scary...

@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 ничего не начинает прокручивать. Он устанавливает все данные, подлежащие прокрутке, но ничего не инициирует.

My Hunch

Когда вы выполняете циклическое действие (от 1 до 7 или от 7 до 1), есть два вызова startScroll (). Я думаю, что что-то между двумя вызовами вызывает проблему.

User scrolls from element 1 to element 7 causing a jump from 0 to 7. This should animate to the left. startScroll() is called indicating a short animation to the left. STUFF HAPPENS THAT MAKES ME CRY PROBABLY I THINK startScroll() is called indicating a long animation to the right. Long animation to the right occurs.

Если я закомментирую 4, то 5 станет «Короткая правильная анимация слева, все сойдет с ума»

Summary

Моя реализация Circular ViewPager работает, но анимация не работает. Попытка исправить анимацию нарушает функциональность ViewPager. В настоящее время я вращаю свои колеса, пытаясь понять, как заставить это работать. Помоги мне! :)

Если что-то неясно, пожалуйста, прокомментируйте ниже, и я уточню. Я понимаю, что был не очень точен в том, как все сломано. Это трудно описать, потому что даже не ясно, что я вижу на экране. Если мое объяснение - проблема, я могу работать над этим, дайте мне знать!

Ура, Колтин

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

Ответы на вопрос(1)

Ваш ответ на вопрос