Cambiar ViewPager para habilitar el desplazamiento infinito de la página

Jon Willis ha publicado cómo habilitar un desplazamiento infinito con su código. Allí dijo que hizo algunos cambios en la clase ViewPager en la biblioteca de soporte de Android. ¿Qué cambios se han realizado y cómo es posible “recomstackr” la biblioteca con el cambio de ViewPager?

Resolví este problema muy simplemente usando un pequeño truco en el adaptador. Aquí está mi código:

public class MyPagerAdapter extends FragmentStatePagerAdapter { public static int LOOPS_COUNT = 1000; private ArrayList mProducts; public MyPagerAdapter(FragmentManager manager, ArrayList products) { super(manager); mProducts = products; } @Override public Fragment getItem(int position) { if (mProducts != null && mProducts.size() > 0) { position = position % mProducts.size(); // use modulo for infinite cycling return MyFragment.newInstance(mProducts.get(position)); } else { return MyFragment.newInstance(null); } } @Override public int getCount() { if (mProducts != null && mProducts.size() > 0) { return mProducts.size()*LOOPS_COUNT; // simulate infinite by big number of products } else { return 1; } } } 

Y luego, en ViewPager, establecemos la página actual en el medio:

 mAdapter = new MyPagerAdapter(getSupportFragmentManager(), mProducts); mViewPager.setAdapter(mAdapter); mViewPager.setCurrentItem(mViewPager.getChildCount() * MyPagerAdapter.LOOPS_COUNT / 2, false); // set current item in the adapter to middle 

Gracias por tu respuesta Shereef.

Lo resolví un poco diferente.

Cambié el código de la clase ViewPager de la biblioteca de soporte de Android. El método setCurrentItem(int)

cambia la página con animación Este método llama a un método interno que requiere el índice y una bandera que permite un desplazamiento suave. Esta bandera es boolean smoothScroll . Extendiendo este método con un segundo parámetro boolean smoothScroll resolvió por mí. Llamar a este método setCurrentItem(int index, boolean smoothScroll) me permitió hacer que se desplace indefinidamente.

Aquí hay un ejemplo completo:

Tenga en cuenta que solo se muestra la página del centro. Además guardé las páginas por separado, lo que me permitió manejarlas con más facilidad.

 private class Page { View page; List<..> data; } // page for predecessor, current, and successor Page[] pages = new Page[3]; mDayPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {} @Override public void onPageScrollStateChanged(int state) { if (state == ViewPager.SCROLL_STATE_IDLE) { if (mFocusedPage == 0) { // move some stuff from the // center to the right here moveStuff(pages[1], pages[2]); // move stuff from the left to the center moveStuff(pages[0], pages[1]); // retrieve new stuff and insert it to the left page insertStuff(pages[0]); } else if (mFocusedPage == 2) { // move stuff from the center to the left page moveStuff(pages[1], pages[0]); // move stuff from the right to the center page moveStuff(pages[2], pages[1]); // retrieve stuff and insert it to the right page insertStuff(pages[2]); } // go back to the center allowing to scroll indefinitely mDayPager.setCurrentItem(1, false); } } }); 

Sin embargo, sin Jon Willis Code no lo habría resuelto yo mismo.

EDITAR: aquí hay un blog sobre esto:

Buscapersonas Infinite al anular 4 métodos de adaptadores en su clase de adaptador existente

  @Override public int getCount() { return Integer.MAX_VALUE; } @Override public CharSequence getPageTitle(int position) { String title = mTitleList.get(position % mActualTitleListSize); return title; } @Override public Object instantiateItem(ViewGroup container, int position) { int virtualPosition = position % mActualTitleListSize; return super.instantiateItem(container, virtualPosition); } @Override public void destroyItem(ViewGroup container, int position, Object object) { int virtualPosition = position % mActualTitleListSize; super.destroyItem(container, virtualPosition, object); } 

Todo lo que necesitas hacer es mirar el ejemplo aquí

Encontrará que en la línea 295, la página siempre se establece en 1 para que sea desplazable y que el recuento de páginas sea 3 en el método getCount() .

Esas son las 2 cosas principales que necesita cambiar, el rest es su lógica y puede manejarlas de manera diferente.

Simplemente haga un contador personal que cuente la página real en la que se encuentra porque la posición ya no será utilizable después de establecer siempre la página actual en 1 en la línea 295.

Si este código no es mío, se lo mencionó en la pregunta que ha vinculado en su pregunta

En realidad, he estado mirando las diversas formas de hacer esta paginación “infinita”, y aunque la noción humana del tiempo es infinita (aunque tengamos una noción del principio y el final del tiempo), las computadoras tratan en lo discreto Hay un tiempo mínimo y máximo (que se puede ajustar a medida que pasa el tiempo, ¿recuerda la base del miedo a Y2K?).

De todos modos, el punto de esta discusión es que es / debería ser suficiente para soportar un rango de fechas relativamente infinito a través de un rango de fechas realmente finito. Un buen ejemplo de esto es la implementación CalendarView Android Framework y el WeeksAdapter dentro de ella. La fecha mínima predeterminada es en 1900 y la fecha máxima predeterminada es en 2100, esto debería cubrir el 99% del uso del calendario de cualquier persona dentro de un radio de 10 años hasta el día de hoy fácilmente.

Lo que hacen en su implementación (centrado en semanas) es calcular el número de semanas entre la fecha mínima y la máxima. Esto se convierte en el número de páginas en el buscapersonas. Recuerde que el buscapersonas no necesita mantener todas estas páginas simultáneamente ( setOffscreenPageLimit(int) ), solo necesita poder crear la página según el número de página (o índice / posición). En este caso, el índice es el número de semanas que la semana es desde la fecha mínima. Con este enfoque, solo tiene que mantener la fecha mínima y el número de páginas (distancia hasta la fecha máxima), luego, para cualquier página, puede calcular fácilmente la semana asociada a esa página. No se baila el hecho de que ViewPager no admite el bucle (también conocido como paginación infinita), y trata de obligarlo a comportarse como si se pudiera desplazar infinitamente.

 new FragmentStatePagerAdapter(getFragmentManager()) { @Override public Fragment getItem(int index) { final Bundle arguments = new Bundle(getArguments()); final Calendar temp_calendar = Calendar.getInstance(); temp_calendar.setTimeInMillis(_minimum_date.getTimeInMillis()); temp_calendar.setFirstDayOfWeek(_calendar.getStartOfWeek()); temp_calendar.add(Calendar.WEEK_OF_YEAR, index); // Moves to the first day of this week temp_calendar.add(Calendar.DAY_OF_YEAR, -UiUtils.modulus(temp_calendar.get(Calendar.DAY_OF_WEEK) - temp_calendar.getFirstDayOfWeek(), 7)); arguments.putLong(KEY_DATE, temp_calendar.getTimeInMillis()); return Fragment.instantiate(getActivity(), WeekDaysFragment.class.getName(), arguments); } @Override public int getCount() { return _total_number_of_weeks; } }; 

Entonces WeekDaysFragment puede mostrar fácilmente la semana que comienza en la fecha pasada en sus argumentos.

Alternativamente, parece que alguna versión de la aplicación Calendario en Android usa un ViewSwitcher (lo que significa que solo hay 2 páginas, la que ve y la página oculta). A continuación, cambia la animación de transición en función de la forma en que el usuario deslizó y representa la página siguiente / anterior en consecuencia. De esta manera, obtiene una paginación infinita porque simplemente cambia entre dos páginas infinitamente. Sin embargo, esto requiere el uso de una View para la página, que es la forma en que fui con el primer enfoque.

En general, si desea “paginación infinita”, es probable que se deba a que sus páginas están basadas en fechas u horas de alguna manera. Si este es el caso, considere usar un subconjunto finito de tiempo que es relativamente infinito en su lugar. Así es como se implementa CalendarView , por ejemplo. O puede usar el enfoque ViewSwitcher . La ventaja de estos dos enfoques es que ninguno hace nada particularmente inusual con ViewSwitcher o ViewPager , y no requiere ningún truco o reimplementación para ViewSwitcher a comportarse infinitamente ( ViewSwitcher ya está diseñado para alternar entre vistas infinitamente, pero ViewPager está diseñado para trabajar en un conjunto de páginas finito, pero no necesariamente constante).

Esqueleto de adaptador deslizante infinito basado en muestras anteriores

algunos problemas críticos:

  • recuerde la posición original (relativa) en la vista de página (etiqueta utilizada en la muestra), así que buscaremos esta posición para definir la posición relativa de la vista. de lo contrario, el orden de los niños en el localizador se mezcla
  • tiene que llenar la vista absoluta por primera vez dentro del adaptador. (el rest de veces este relleno no será válido) no encontró forma de forzarlo a llenar desde el controlador del localizador. el rest de la vista absoluta se anulará del controlador del localizador con los valores correctos.
  • cuando las páginas se deslizan rápidamente, la página secundaria (en realidad, izquierda) no se llena desde el controlador del localizador. no hay solución por el momento, solo use la vista vacía, se llenará con los valores reales cuando se detiene el arrastre. upd: solución rápida: deshabilitar el destroyItem del adaptador.

Puede ver el logcat para entender qué está pasando en esta muestra

     

Y entonces:

 public class ActivityCalendar extends Activity { public class CalendarAdapter extends PagerAdapter { @Override public int getCount() { return 3; } @Override public boolean isViewFromObject(View view, Object object) { return view == ((RelativeLayout) object); } @Override public Object instantiateItem(ViewGroup container, int position) { LayoutInflater inflater = (LayoutInflater)ActivityCalendar.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View viewLayout = inflater.inflate(R.layout.layout_calendar, container, false); viewLayout.setTag(new Integer(position)); //TextView tv = (TextView) viewLayout.findViewById(R.id.calendar_text); //tv.setText(String.format("Text Text Text relative: %d", position)); if (!ActivityCalendar.this.scrolledOnce) { // fill here only first time, the rest will be overriden in pager scroll handler switch (position) { case 0: ActivityCalendar.this.setPageContent(viewLayout, globalPosition - 1); break; case 1: ActivityCalendar.this.setPageContent(viewLayout, globalPosition); break; case 2: ActivityCalendar.this.setPageContent(viewLayout, globalPosition + 1); break; } } ((ViewPager) container).addView(viewLayout); //Log.i("instantiateItem", String.format("position = %d", position)); return viewLayout; } @Override public void destroyItem(ViewGroup container, int position, Object object) { ((ViewPager) container).removeView((RelativeLayout) object); //Log.i("destroyItem", String.format("position = %d", position)); } } public void setPageContent(View viewLayout, int globalPosition) { if (viewLayout == null) return; TextView tv = (TextView) viewLayout.findViewById(R.id.calendar_text); tv.setText(String.format("Text Text Text global %d", globalPosition)); } private boolean scrolledOnce = false; private int focusedPage = 0; private int globalPosition = 0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_calendar); final ViewPager viewPager = (ViewPager) findViewById(R.id.pager); viewPager.setOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { focusedPage = position; // actual page change only when position == 1 if (position == 1) setTitle(String.format("relative: %d, global: %d", position, globalPosition)); Log.i("onPageSelected", String.format("focusedPage/position = %d, globalPosition = %d", position, globalPosition)); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { //Log.i("onPageScrolled", String.format("position = %d, positionOffset = %f", position, positionOffset)); } @Override public void onPageScrollStateChanged(int state) { Log.i("onPageScrollStateChanged", String.format("state = %d, focusedPage = %d", state, focusedPage)); if (state == ViewPager.SCROLL_STATE_IDLE) { if (focusedPage == 0) globalPosition--; else if (focusedPage == 2) globalPosition++; scrolledOnce = true; for (int i = 0; i < viewPager.getChildCount(); i++) { final View v = viewPager.getChildAt(i); if (v == null) continue; // reveal correct child position Integer tag = (Integer)v.getTag(); if (tag == null) continue; switch (tag.intValue()) { case 0: setPageContent(v, globalPosition - 1); break; case 1: setPageContent(v, globalPosition); break; case 2: setPageContent(v, globalPosition + 1); break; } } Log.i("onPageScrollStateChanged", String.format("globalPosition = %d", globalPosition)); viewPager.setCurrentItem(1, false); } } }); CalendarAdapter calendarAdapter = this.new CalendarAdapter(); viewPager.setAdapter(calendarAdapter); // center item viewPager.setCurrentItem(1, false); } } 

Hackeado por CustomPagerAdapter :

MainActivity.java :

 import android.content.Context; import android.os.Handler; import android.os.Parcelable; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private List numberList = new ArrayList(); private CustomPagerAdapter mCustomPagerAdapter; private ViewPager mViewPager; private Handler handler; private Runnable runnable; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); numberList.clear(); for (int i = 0; i < 10; i++) { numberList.add(""+i); } mViewPager = (ViewPager)findViewById(R.id.pager); mCustomPagerAdapter = new CustomPagerAdapter(MainActivity.this); EndlessPagerAdapter mAdapater = new EndlessPagerAdapter(mCustomPagerAdapter); mViewPager.setAdapter(mAdapater); mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { int modulo = position%numberList.size(); Log.i("Current ViewPager View's Position", ""+modulo); } @Override public void onPageScrollStateChanged(int state) { } }); handler = new Handler(); runnable = new Runnable() { @Override public void run() { mViewPager.setCurrentItem(mViewPager.getCurrentItem()+1); handler.postDelayed(runnable, 1000); } }; handler.post(runnable); } @Override protected void onDestroy() { if(handler!=null){ handler.removeCallbacks(runnable); } super.onDestroy(); } private class CustomPagerAdapter extends PagerAdapter { Context mContext; LayoutInflater mLayoutInflater; public CustomPagerAdapter(Context context) { mContext = context; mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public int getCount() { return numberList.size(); } @Override public boolean isViewFromObject(View view, Object object) { return view == ((LinearLayout) object); } @Override public Object instantiateItem(ViewGroup container, int position) { View itemView = mLayoutInflater.inflate(R.layout.row_item_viewpager, container, false); TextView textView = (TextView) itemView.findViewById(R.id.txtItem); textView.setText(numberList.get(position)); container.addView(itemView); return itemView; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((LinearLayout) object); } } private class EndlessPagerAdapter extends PagerAdapter { private static final String TAG = "EndlessPagerAdapter"; private static final boolean DEBUG = false; private final PagerAdapter mPagerAdapter; EndlessPagerAdapter(PagerAdapter pagerAdapter) { if (pagerAdapter == null) { throw new IllegalArgumentException("Did you forget initialize PagerAdapter?"); } if ((pagerAdapter instanceof FragmentPagerAdapter || pagerAdapter instanceof FragmentStatePagerAdapter) && pagerAdapter.getCount() < 3) { throw new IllegalArgumentException("When you use FragmentPagerAdapter or FragmentStatePagerAdapter, it only supports >= 3 pages."); } mPagerAdapter = pagerAdapter; } @Override public void destroyItem(ViewGroup container, int position, Object object) { if (DEBUG) Log.d(TAG, "Destroy: " + getVirtualPosition(position)); mPagerAdapter.destroyItem(container, getVirtualPosition(position), object); if (mPagerAdapter.getCount() < 4) { mPagerAdapter.instantiateItem(container, getVirtualPosition(position)); } } @Override public void finishUpdate(ViewGroup container) { mPagerAdapter.finishUpdate(container); } @Override public int getCount() { return Integer.MAX_VALUE; // this is the magic that we can scroll infinitely. } @Override public CharSequence getPageTitle(int position) { return mPagerAdapter.getPageTitle(getVirtualPosition(position)); } @Override public float getPageWidth(int position) { return mPagerAdapter.getPageWidth(getVirtualPosition(position)); } @Override public boolean isViewFromObject(View view, Object o) { return mPagerAdapter.isViewFromObject(view, o); } @Override public Object instantiateItem(ViewGroup container, int position) { if (DEBUG) Log.d(TAG, "Instantiate: " + getVirtualPosition(position)); return mPagerAdapter.instantiateItem(container, getVirtualPosition(position)); } @Override public Parcelable saveState() { return mPagerAdapter.saveState(); } @Override public void restoreState(Parcelable state, ClassLoader loader) { mPagerAdapter.restoreState(state, loader); } @Override public void startUpdate(ViewGroup container) { mPagerAdapter.startUpdate(container); } int getVirtualPosition(int realPosition) { return realPosition % mPagerAdapter.getCount(); } PagerAdapter getPagerAdapter() { return mPagerAdapter; } } } 

activity_main.xml :

      

row_item_viewpager.xml :

     

Hecho

Para el desplazamiento infinito con días es importante que tengas el buen fragmento en el buscapersonas, por lo tanto escribí mi respuesta en esta página ( Viewpager en Android para cambiar entre días interminablemente )

¡Está funcionando muy bien! Las respuestas anteriores no me funcionaron porque quería que funcionaran.

Creé una biblioteca que puede hacer que cualquier ViewPager, pagerAdapter (o FragmentStatePagerAdapter) y TabLayout opcional se desplacen infinitamente.

https://github.com/memorex386/infinite-scroll-viewpager-w-tabs