ViewPager как круговая очередь / обертывание
Я использую ViewPager
с FragmentStatePagerAdapter
разрешить навигацию между некоторыми фрагментами.
скажем, у меня есть три фрагмента: A
,B
и C
. ViewPager показывает фрагмент A изначально и позволяет перейти к фрагменту B, проводя справа налево, а затем к фрагменту C, проводя снова. Это позволяет использовать следующие пути навигации:A <--> B <--> C
.
Я хотел бы иметь возможность прокручивать слева направо на фрагменте A и иметь ViewPager показывает фрагмент C, т. е. для того, чтобы он вел себя как круговая очередь и позволял ... --> C <--> A <--> B <--> C <--> A <-- ...
Я не хочу, чтобы фрагменты дублировались в других позициях (т. е. заканчивались более чем тремя экземплярами).
возможна ли эта функция обертывания с помощью ViewPager?
12 ответов:
я реализовал ViewPager/PagerAdapter, который может позволить псевдо-бесконечное поведение подкачки. Он работает, указывая очень большое число в качестве фактического количества, но сопоставляет их с фактическим диапазоном набора данных/набора страниц. Он смещает начало на большое число также Так, что вы можете сразу же прокрутить влево от "первой" страницы.
Он не работает так хорошо, как только вы доберетесь до 1,000,000-й страницы (вы увидите графические глюки при прокрутке), но это, как правило, не a реальный случай использования. Я мог бы исправить это, сбросив счетчик на более низкое число раз в то время, но я оставлю его, как это сейчас.
The
InfinitePagerAdapter
обертывает существующий ViewPager, и поэтому использование довольно прозрачно. ЭлементInfiniteViewPager
совсем немного работы, чтобы убедиться, что вы можете прокручивать влево и вправо много раз.
это решение без поддельных страниц и работает как шарм:
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));
Это почти выполняет то, что хочет антонит, он не позволит вам уйти от 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 } }
только сейчас увидел этот вопрос и нашли решение. Решение По Ссылке
внесите следующие изменения в свою функцию 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 здесь.
надеюсь, что это помогает.
другой способ сделать это-добавить поддельную копию первого элемента в пейджер представления в конец вашего адаптера и поддельную копию последнего элемента в начало. Затем, когда вы просматриваете первый / последний элемент на пейджере просмотра, измените страницы без анимации.
Итак, в основном, первые / последние элементы в вашем пейджере просмотра являются подделками, просто там, чтобы произвести правильную анимацию, а затем переключиться на конец/начало. Пример:
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); } } }
простая идея, как этого добиться. Когда у вас есть свой массив со страницами, просто добавьте этот массив в другой массив точно посередине.
например, у вас есть массив из 15 элементов. Поэтому поместите его в массив 400 элементов в середине (200 позиция)
далее просто заполните массив слева и справа, чтобы вы могли перемещаться.
пример изображения:
Как это выглядит?
пусть код говорит:)
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; }
у меня есть решение, я думаю, будет работать. Он не тестируется, но вот он:
обертка вокруг FragmentPagerAdapter как супер-класс:
public abstract class AFlexibleFragmentPagerAdapter extends FragmentPagerAdapter { public AFlexibleFragmentPagerAdapter(FragmentManager fm) { super(fm); // TODO Auto-generated constructor stub } public abstract int getRealCount(); public abstract int getStartPosition(); public abstract int transformPosition(int position); };
затем стандартный подкласс реализации FragmentPagerAdapter этот новый абстрактный класс:
public class SomeFragmentPagerAdapter extends AFlexibleFragmentPagerAdapter { private Collection<Fragment> someContainer; public SomeFragmentPagerAdapter(FragmentManager fm, Container<Fragment> fs) { super(fm); someContainer = fs; } @Override public int getRealCount() { return someContainer.size(); } @Override public int getStartPosition() { return 0; } @Override public int transformPosition(int position) { return position; } };
и реализация адаптера циклического пейджера.
public class SomeCyclicFragmentPagerAdapter extends SomeFragmentPagerAdapter { private int realCount = 0; private int dummyCount = 0; private static final int MULTI = 3; public SomeFragmentPagerAdapter(FragmentManager fm, ...) { super(fm); realCount = super.getCount(); dummyCount *= MULTI; } @Override public int getCount() { return dummyCount; } @Override public int getRealCount() { return realCount; } @Override public int getStartPosition() { return realCount; } @Override public int transformPosition(int position) { if (position < realCount) position += realCount; else if (position >= (2 * realCount)) position -= realCount; return position; } };
инициализация ViewPager
this.mViewPager.setCurrentItem(this.mPagerAdapter.getStartPosition());
и, наконец, реализация обратного вызова:
@Override public void onPageSelected(int position) { this.mTabHost.setCurrentTab(position % mPagerAdapter.getRealCount()); this.mViewPager.setCurrentItem(this.mPagerAdapter .transformPosition(position)); } @Override public void onTabChanged(String tabId) { int pos = this.mTabHost.getCurrentTab(); this.mViewPager.setCurrentItem(this.mPagerAdapter .transformPosition(pos)); }
извините за задержку, но я видел ответы, и я не смог удержаться, тоже должен был ответить :).
на вашем адаптере, на методе get count, действуйте следующим образом:
@Override public int getCount() { return myList.size() % Integer.MAX_VALUE; }
Это будет работать!
- установить для элементов подсчета некоторое большое значение, скажем 500000.
- возвращает из адаптера это значение * количество фактических элементов.
- на getItem (int i) отрегулируйте i на -> i = i % количество фактических элементов.
на onCreate (Bundle savedInstanceState) установите текущий элемент для страницы 500000/2
public class MainActivity extends FragmentActivity { private final static int ITEMS_MAX_VALUE = 500000; private int actualNumCount = 10; private ViewPager mPager = null; private class OptionPageAdapter extends FragmentStatePagerAdapter { public OptionPageAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int i) { try { i = i % OptionType.values().length; OptionPage optionPage = new OptionPage(i); optionPage.id = i; return optionPage; } catch (Exception e) { VayoLog.log(Level.WARNING, "Unable to create OptionFragment for id: " + i, e); } return null; } @Override public int getCount() { return ITEMS_MAX_VALUE * actualNumCount; } } public static class OptionPage extends Fragment { private int id = 0 @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View mainView = inflater.inflate(R.layout.main_page, container, false); return mainView; } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mPager = (ViewPager) findViewById(R.id.main_pager); mPager.setOffscreenPageLimit(3); mPager.setAdapter(new OptionPageAdapter(this.getSupportFragmentManager())); mPager.setCurrentItem(ITEMS_MAX_VALUE/2,false); } }
- добавить поддельную копию последнего элемента в начале списка.
- добавить поддельную копию первого элемента в конце.
выберите настоящий первый элемент где-то в коде инициализации:
mViewPager.setCurrentItem(1);
изменить 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); } }
на основе этот подход
истинная петля, пейджер названием прокладка, правда не обновить прокрутки
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; } } });
вы можете использовать простой вид из 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 } }); } }