ViewPager como una cola / envoltura circular

Estoy usando ViewPager con FragmentStatePagerAdapter para permitir la navegación entre algunos fragmentos.

Digamos que tengo tres fragmentos: A , B y C El ViewPager muestra inicialmente el Fragmento A, y le permite navegar al Fragmento B deslizando de derecha a izquierda, y luego al Fragmento C al deslizar de nuevo. Esto permite las siguientes rutas de navegación: A B C

Lo que me gustaría es poder pasar de izquierda a derecha en el Fragmento A y hacer que ViewPager muestre el Fragmento C, es decir, que se comporte como una cola circular y permita ... --> C A B C A <-- ...

No quiero duplicar los Fragmentos en otras posiciones (es decir, terminar con más de tres instancias).

¿Es posible esta funcionalidad de envoltura con un ViewPager?

Implementé ViewPager / PagerAdapter que puede permitir el comportamiento de búsqueda pseudo infinito. Funciona al especificar un número muy grande como el recuento real, pero los asigna al rango real del conjunto de datos / conjunto de páginas. También compensa el comienzo con un número grande para que pueda desplazarse inmediatamente hacia la izquierda desde la ‘primera’ página.

No funciona tan bien una vez que llegas a la página 1,000,000 (verás fallas gráficas al desplazarte), pero esto normalmente no es un caso de uso del mundo real. Podría arreglar esto restableciendo el recuento a un número más bajo de vez en cuando, pero lo dejaré como está por ahora.

El InfinitePagerAdapter envuelve un ViewPager existente, por lo que el uso es bastante transparente. El InfiniteViewPager hace un poco de trabajo para asegurarse de que puede desplazarse hacia la izquierda y la derecha muchas veces.

https://github.com/antonyt/InfiniteViewPager

Esta es una solución sin páginas falsas y funciona como un encanto:

 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) { } } 

Solo tiene que configurarlo en su ViewPager como en PageChangeListener y eso es todo: (** obsoleto ahora ** verifique las notas de edición)

 viewPager.setOnPageChangeListener(new CircularViewPagerHandler(viewPager)); 

Para evitar que este brillo azul en el “final” de su ViewPager debe aplicar esta línea a su xml donde se coloca ViewPager:

 android:overScrollMode="never" 

Hemos mejorado la solución anterior y creamos una pequeña biblioteca en github . No dude en echarle un vistazo 🙂

Editar :: De acuerdo con el comentario de @Bhavana, solo use addOnPageChangeListener en lugar de setOnPageChangeListener, ya que más tarde está en desuso.

  viewPager.addOnPageChangeListener(new CircularViewPagerHandler(viewPager)); 

Esto casi logra lo que quiere antonyt, pero no te deja ir de A -> C. (No probado enérgicamente, pero parece funcionar bien)

 public class MyAdapter extends PagerAdapter { private List mItems; private int mFakeCount = 0; public MyAdapter(Context context, List 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 } } 

Otra forma de hacerlo es agregar una copia falsa del primer elemento en el buscapersonas al final de su adaptador, y una copia falsa del último elemento al principio. Luego, cuando esté viendo el primer / último elemento en el buscapersonas, cambie las páginas sin animar.

Así que, básicamente, los primeros / últimos elementos en su paginador de vista son falsos, solo para producir la animación correcta, y luego cambiar al final / principio. Ejemplo:

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

Justo ahora vi esta pregunta y encontré una solución. Enlace de solución

Realice los siguientes cambios en su función onPageScrollStateChanged. Considera que tienes 4 tabs.

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

Si la variable de estado está en las tabs existentes, su valor es 1.

Se convierte en 0 una vez que intenta navegar antes de su primera pestaña o después de su última pestaña. Por lo tanto, compare los estados anterior y actual, como se muestra en el código y listo.

Consulte la función onPageScrollStateChanged aquí .

Espero eso ayude.

Idea simple de cómo lograr esto. Cuando tenga su matriz con páginas simplemente agregue esta matriz a otra matriz exactamente en el medio.

Por ejemplo, tienes una matriz con 15 elementos. Así que colóquelo en una matriz de 400 elementos en el medio (posición 200)

A continuación, simplemente llene la matriz a la izquierda y derecha para que pueda moverse.

Imagen de muestra:

enter image description here

¿Como luce?

https://youtu.be/7up36ylTzXk

Deja que el código hable 🙂

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 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 _fragments = new ArrayList<>(); public ArrayList _fragmentsRaw; } 

Tengo una solución que creo que funcionará. No está probado, pero aquí está:

Un contenedor alrededor de FragmentPagerAdapter como superclase:

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

A continuación, la subclase de implementación FragmentPagerAdapter estándar esta nueva clase abstracta:

 public class SomeFragmentPagerAdapter extends AFlexibleFragmentPagerAdapter { private Collection someContainer; public SomeFragmentPagerAdapter(FragmentManager fm, Container 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; } }; 

Y la implementación del Adaptador de radiolocalizador cíclico.

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

Inicialización de ViewPager

  this.mViewPager.setCurrentItem(this.mPagerAdapter.getStartPosition()); 

Y finalmente, la implementación de callback:

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

Perdón por la demora, pero he visto las respuestas y II no pudo contener, también tengo que contestar :).

En su adaptador, en el método get count, proceda de la siguiente manera:

  @Override public int getCount() { return myList.size() % Integer.MAX_VALUE; } 

¡Que funcionará!

  1. El conjunto para los artículos cuenta con un valor grande, digamos 500000.
  2. Devuelve del adaptador este valor * número de elementos reales.
  3. En getItem (int i) ajuste i a -> i = i% número de elementos reales.
  4. En onCreate (Bundle savedInstanceState) establezca el elemento actual para la página 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); } } 
  1. agregue una copia falsa del último elemento al principio de su lista.
  2. agregue una copia falsa del primer elemento al final.
  3. seleccione el primer elemento real en algún lugar del código de inicio:

      mViewPager.setCurrentItem(1); 
  4. modificar 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); } } 

basado en este enfoque

verdadero bucle, título del buscapersonas, verdadero sin desplazamiento de actualización

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

ver el adaptador de página:

 public static class MyFragmentPageAdapter extends FragmentPagerAdapter { private HashMap mItems = new HashMap(); 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; } } 

utilizando:

  itemList = new ArrayList(); fragments = new ArrayList(); titles = new ArrayList(); 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; } } }); 

Puede usar una vista simple de https://github.com/pozitiffcat/cyclicview
Una de las características es:

No duplique la primera y la última vista, utilice las variantes de bitmap en su lugar

Ejemplo:

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