ViewPager как круговая очередь / перенос - PullRequest
63 голосов
/ 25 сентября 2011

Я использую ViewPager с FragmentStatePagerAdapter, чтобы разрешить навигацию между некоторыми фрагментами.

Допустим, у меня есть три фрагмента: A, B и C.ViewPager сначала показывает фрагмент A и позволяет вам перейти к фрагменту B, проводя справа налево, а затем к фрагменту C, проводя снова.Это позволяет использовать следующие навигационные пути: A <--> B <--> C.

. Мне хотелось бы иметь возможность проводить пальцем слева направо на фрагменте A и иметь ViewPager, отображающий фрагмент C, т.е. чтобы он вел себя каккруговая очередь и разрешить ... --> C <--> A <--> B <--> C <--> A <-- ...

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

Возможна ли эта функция обертывания с помощью ViewPager?

Ответы [ 13 ]

48 голосов
/ 11 марта 2012

Я реализовал ViewPager / PagerAdapter, который допускает псевдо-бесконечное поведение подкачки. Он работает путем указания очень большого числа в качестве фактического количества, но отображает их в фактический диапазон набора данных / набора страниц. Начало также смещается на большое число, так что вы можете сразу же прокрутить влево от первой страницы.

Он не работает так хорошо, как только вы доберетесь до 1 000 000 страниц (вы увидите графические глюки при прокрутке), но это, как правило, не реальный вариант использования. Я мог бы исправить это, время от времени сбрасывая счетчик на более низкое число, но сейчас я оставлю все как есть.

InfinitePagerAdapter оборачивает существующий ViewPager, поэтому его использование довольно прозрачно. InfiniteViewPager делает небольшую работу, чтобы убедиться, что вы потенциально можете прокручивать влево и вправо много раз.

https://github.com/antonyt/InfiniteViewPager

16 голосов
/ 08 июня 2015

Это решение без поддельных страниц и работает как шарм:

public class CircularViewPagerHandler implements ViewPager.OnPageChangeListener {
    private ViewPager   mViewPager;
    private int         mCurrentPosition;
    private int         mScrollState;

    public CircularViewPagerHandler(final ViewPager viewPager) {
        mViewPager = viewPager;
    }

    @Override
    public void onPageSelected(final int position) {
        mCurrentPosition = position;
    }

    @Override
    public void onPageScrollStateChanged(final int state) {
        handleScrollState(state);
        mScrollState = state;
    }

    private void handleScrollState(final int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            setNextItemIfNeeded();
        }
    }

    private void setNextItemIfNeeded() {
        if (!isScrollStateSettling()) {
            handleSetNextItem();
        }
    }

    private boolean isScrollStateSettling() {
        return mScrollState == ViewPager.SCROLL_STATE_SETTLING;
    }

    private void handleSetNextItem() {
        final int lastPosition = mViewPager.getAdapter().getCount() - 1;
        if(mCurrentPosition == 0) {
            mViewPager.setCurrentItem(lastPosition, false);
        } else if(mCurrentPosition == lastPosition) {
            mViewPager.setCurrentItem(0, false);
        }
    }

    @Override
    public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
    }
}

Вам просто нужно установить его в ViewPager как onPageChangeListener и все: (** устарело * ** проверьте примечания к редактированию)

viewPager.setOnPageChangeListener(new CircularViewPagerHandler(viewPager));

Чтобы избежать этого синего блеска в «конце» вашего ViewPager, вы должны применить эту строку к вашему xml, где находится ViewPager:

android:overScrollMode="never"

Мы улучшили решение выше и создали небольшую библиотеку на github .Не стесняйтесь проверить это:)

Edit :: В соответствии с комментарием @Bhavana, просто используйте addOnPageChangeListener вместо setOnPageChangeListener, так как позднее это устарело.

 viewPager.addOnPageChangeListener(new CircularViewPagerHandler(viewPager));
7 голосов
/ 29 августа 2014

Другой способ сделать это - добавить поддельную копию первого элемента в пейджере представления в конец вашего адаптера и поддельную копию последнего элемента в начало.Затем, когда вы просматриваете первый / последний элемент в пейджере просмотра, меняйте страницы без анимации.

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

list.add(list.get(0)); //add the first item again as the last, to create the wrap effect
list.add(0, list.get(list.size()-2)); //insert a copy of the last item as the new first item, so we can scroll backwards too

viewPager.setAdapter(list);
viewPager.setCurrentItem(1, false); //default to the second element (because the first is now a fake)

viewPager.setOnPageChangeListener(new OnPageChangeListener() {
private void handleScrollState(final int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) { //this is triggered when the switch to a new page is complete
            final int lastPosition = viewPager.getAdapter().getCount() - 1;
            if (currentPosition == lastPosition) {
                viewPager.setCurrentItem(1, false); //false so we don't animate
            } else if (currentPosition == 0) {
                viewPager.setCurrentItem(lastPosition -1, false);
            }
        }

}
7 голосов
/ 27 января 2014

Только сейчас увидел этот вопрос и нашел решение. Solution Link

Внесите следующие изменения в функцию onPageScrollStateChanged. Предположим, у вас есть 4 вкладок.

private int previousState, currentState;

public void onPageScrollStateChanged(int state) {              

            int currentPage = viewPager.getCurrentItem();       //ViewPager Type
            if (currentPage == 3 || currentPage == 0){
                previousState = currentState;
                currentState = state;
                if (previousState == 1 && currentState == 0){

                    viewPager.setCurrentItem(currentPage == 0 ? 3 : 0);

                }

            }

        }

Если переменная state находится на существующих вкладках, ее значение равно 1.

Становится равным 0, когда вы пытаетесь перейти до вашей первой вкладки или после вашей последней вкладки. Итак, сравните предыдущее и текущее состояния, как показано в коде, и все готово.

См. Функцию onPageScrollStateChanged здесь .

Надеюсь, это поможет.

6 голосов
/ 29 ноября 2011

Это почти выполняет то, что хочет Антонит, но оно не отпустит вас от A -> C.(Не проверено энергично, но, кажется, работает достаточно хорошо)

public class MyAdapter extends PagerAdapter {
    private List<Object> mItems;
    private int mFakeCount = 0;

    public MyAdapter(Context context, List<Object> items) {
        mItems = items;
        mFakeCount = mItems.size()+1;
    }

    @Override
    public int getCount() {
        return mFakeCount;
    }

    @Override
    public Object instantiateItem(View collection, int position) {
        // make the size larger, and change the position
        // to trick viewpager into paging forever
        if (position >= mItems.size()-1) {
            int newPosition = position%mItems.size();

            position = newPosition;
            mFakeCount++;
        }

        // do your layout inflating and what not here.
        // position will now refer to a correct index into your mItems list
        // even if the user has paged so many times that the view has wrapped
    }
}
3 голосов
/ 12 июля 2019

Я пробовал все предложения, решения, библиотеки и т. Д., Но они не являются круговыми, и большую часть времени не поддерживают только 3 страницы.

Итак, я реализовал круговой ViewPager пример с использованием нового ViewPager2, новый ViewPager использует RecyclerView и ViewHolder s для обработки повторного просмотра и работает как ожидалось!

TLDR: GITHUB

В этом примере будет создаваться одно приложение для занятий с ViewPager2 и FragmentPagerAdapter, поддерживающее круговую навигацию между 3 страницами или более.

Я использую альфа-версию библиотеки androidx.viewpager2:viewpager2, но версия 1.0.0-alpha06 является последней из запланированных до того, как Google заморозил API и перешел на бета-версию.

enter image description here

1. Добавьте библиотеку ViewPager2 к зависимостям в вашем build.gradle

dependencies {
    implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha06'
}

2. Добавьте ViewPager2 представление к вашему проекту:

<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/vwpHome"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3. Создайте адаптер FragmentStateAdapter:

getItemCount() необходимо вернуть номер huuuuge. (2147483647)

getCenterPage() возвращает центральную страницу на основе номера huuuuge. Этот метод используется для получения позиции начальной страницы для установки в ViewPager2, в этом случае пользователю необходимо провести по ˜1073741823 разу, чтобы достичь конца ViewPager2.

class CircularPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) {

    override fun getItemCount() = Integer.MAX_VALUE

    /**
     * Create the fragment based on the position
     */
    override fun createFragment(position: Int) = HomePagerScreens.values()[position % HomePagerScreens.values().size].fragment.java.newInstance()

    /**
     * Returns the same id for the same Fragment.
     */
    override fun getItemId(position: Int): Long = (position % HomePagerScreens.values().size).toLong()

    fun getCenterPage(position: Int = 0) = Integer.MAX_VALUE / 2 + position
}

HomeScreens - это ENUM с информацией о странице.

enum class HomePagerScreens(@StringRes val title: Int,
                            val fragment: KClass<out Fragment>) {

    HOME_1(R.string.home_1, FragmentHome::class),
    HOME_2(R.string.home_2, FragmentHome::class),
    HOME_3(R.string.home_3, FragmentHome::class)
}

4. Установите адаптер для ViewPager

val circularAdapter = CircularPagerAdapter(supportFragmentManager, lifecycle)
vwpHome.apply {
    adapter = circularAdapter
    setCurrentItem(circularAdapter.getCenterPage(), false)
}
1 голос
/ 07 октября 2015

Простая идея, как этого добиться.Если у вас есть Array со страницами, просто добавьте этот массив в другой массив точно посередине.

Например, у вас есть массив с 15 элементами.Так что поместите его в массив из 400 элементов посередине (200 позиций)

Затем просто заполните массив влево и вправо, чтобы вы могли перемещаться.enter image description here

Как это выглядит?

https://youtu.be/7up36ylTzXk

Пусть код говорит:)

https://gist.github.com/gelldur/051394d10f6f98e2c13d

public class CyclicPagesAdapter extends FragmentStatePagerAdapter {

    public CyclicPagesAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return _fragments.get(position).createInstance();
    }

    @Override
    public int getCount() {
        return _fragments.size();
    }

    public void addFragment(FragmentCreator creator) {
        _fragments.add(creator);
    }

    public interface FragmentCreator {
        Fragment createInstance();
    }

    public void clear() {
        _fragments.clear();
    }

    public void add(FragmentCreator fragmentCreator) {
        _fragments.add(fragmentCreator);
    }

    public void finishAdding() {
        ArrayList<FragmentCreator> arrayList = new ArrayList<>(Arrays.asList(new FragmentCreator[MAX_PAGES]));
        arrayList.addAll(MAX_PAGES / 2, _fragments);

        int mrPointer = _fragments.size() - 1;
        for (int i = MAX_PAGES / 2 - 1; i > -1; --i) {
            arrayList.set(i, _fragments.get(mrPointer));
            --mrPointer;
            if (mrPointer < 0) {
                mrPointer = _fragments.size() - 1;
            }
        }

        mrPointer = 0;
        for (int i = MAX_PAGES / 2 + _fragments.size(); i < arrayList.size(); ++i) {
            arrayList.set(i, _fragments.get(mrPointer));
            ++mrPointer;
            if (mrPointer >= _fragments.size()) {
                mrPointer = 0;
            }
        }
        _fragmentsRaw = _fragments;
        _fragments = arrayList;
    }

    public int getStartIndex() {
        return MAX_PAGES / 2;
    }

    public int getRealPagesCount() {
        return _fragmentsRaw.size();
    }

    public int getRealPagePosition(int index) {
        final FragmentCreator fragmentCreator = _fragments.get(index);
        for (int i = 0; i < _fragmentsRaw.size(); ++i) {
            if (fragmentCreator == _fragmentsRaw.get(i)) {
                return i;
            }
        }
        //Fail...
        return 0;
    }

    private static final int MAX_PAGES = 500;
    public ArrayList<FragmentCreator> _fragments = new ArrayList<>();
    public ArrayList<FragmentCreator> _fragmentsRaw;
}
0 голосов
/ 13 октября 2016

Вы можете использовать простое представление из https://github.com/pozitiffcat/cyclicview
Одна из функций:

Не дублировать первое и последнее представления, вместо них используются варианты растрового изображения

Пример:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CyclicView cyclicView = (CyclicView) findViewById(R.id.cyclic_view);
        cyclicView.setAdapter(new CyclicAdapter() {
            @Override
            public int getItemsCount() {
                return 10;
            }

            @Override
            public View createView(int position) {
                TextView textView = new TextView(MainActivity.this);
                textView.setText(String.format("TextView #%d", position + 1));
                return textView;
            }

            @Override
            public void removeView(int position, View view) {
                // Do nothing
            }
        });
    }
}
0 голосов
/ 04 июля 2016

на основе этот подход

истинный цикл, полоса заголовка пейджера, true без прокрутки обновления

private static final int ITEM_NUM = 5 ; // 5 for smooth pager title strip
private void addFragment(String title, String className) {
    if (fragments.size() < ITEM_NUM) {
        Bundle b = new Bundle();
        b.putInt("pos", fragments.size());
        fragments.add(Fragment.instantiate(this, className, b));

    }
    titles.add(title);
    itemList.add(className);
}

просмотр страницы адаптера:

public static class MyFragmentPageAdapter extends FragmentPagerAdapter {

    private HashMap<Long, Fragment> mItems = new HashMap<Long, Fragment>();

    public MyFragmentPageAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }

    @Override
    public long getItemId(int position) {
        int id = java.lang.System.identityHashCode(fragments.get(position));
        return id;
    }

    @Override
    public Fragment getItem(int position) {
        long id = getItemId(position);
        if (mItems.get(id) != null) {
            return mItems.get(id);
        }
        Fragment f = fragments.get(position);
        return f;
    }

    @Override
    public int getCount() {
        return ITEM_NUM;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        String t = titles.get(getItem(position).getArguments()
                .getInt("pos"));
        return t;
    }

    @Override
    public int getItemPosition(Object object) {
        for (int i = 0; i < ITEM_NUM; i++) {
            Fragment item = (Fragment) fragments.get(i);
            if (item.equals((Fragment) object)) {
                return i;
            }
        }
        return POSITION_NONE;
    }
}

используя:

    itemList = new ArrayList<String>();
    fragments = new ArrayList<Fragment>();
    titles = new ArrayList<String>();

    addFragment("Title1", Fragment1.class.getName());
    ...
    addFragment("TitleN", FragmentN.class.getName());

    mAdapter = new MyFragmentPageAdapter(getSupportFragmentManager());
    mViewPager = (ViewPager) findViewById(R.id...);

    mViewPager.setAdapter(mAdapter);
    mViewPager.setCurrentItem(2);
    currPage = 2;
    visibleFragmentPos = 2;

    mViewPager.setOnPageChangeListener(new OnPageChangeListener() {

        @Override
        public void onPageSelected(int curr) {
            dir = curr - currPage;
            currPage = curr;
        }

        private void shiftRight() {
            fragments.remove(0);
            int pos = visibleFragmentPos + 3;
            if (pos > itemList.size() - 1)
                pos -= itemList.size();
            if (++visibleFragmentPos > itemList.size() - 1)
                visibleFragmentPos = 0;
            insertItem(4, pos);

        }

        private void shiftLeft() {
            fragments.remove(4);
            int pos = visibleFragmentPos - 3;
            if (pos < 0)
                pos += itemList.size();
            if (--visibleFragmentPos < 0)
                visibleFragmentPos = itemList.size() - 1;
            insertItem(0, pos);
        }

        private void insertItem(int datasetPos, int listPos) {
            Bundle b = new Bundle();
            b.putInt("pos", listPos);
            fragments.add(datasetPos,
                    Fragment.instantiate(ctx, itemList.get(listPos), b));
        }

        @Override
        public void onPageScrollStateChanged(int state) {

            if (state == ViewPager.SCROLL_STATE_IDLE) {
                if (dir == 1) {
                    shiftRight();
                } else if (dir == -1) {
                    shiftLeft();

                }
                mAdapter.notifyDataSetChanged();
                currPage = 2;
            }
        }
    });
0 голосов
/ 13 мая 2016
  1. добавление поддельной копии последнего элемента в начале вашего списка.
  2. добавление поддельной копии первого элемента в конце.
  3. выбор реального первого элементагде-то в коде инициализации:

        mViewPager.setCurrentItem(1);
    
  4. изменить setOnPageChangeListener:

        @Override
        public void onPageSelected(int state) {
            if (state == ITEMS_NUMBER + 1) {
                mViewPager.setCurrentItem(1, false);
            } else if (state == 0) {
                mViewPager.setCurrentItem(ITEMS_NUMBER, false);
            }
        }
    
...