Modification de ViewPager pour activer le défilement illimité des pages

Jon Willis a posté comment activer un défilement infini avec son code. Là-bas, il a dit qu’il avait apporté quelques modifications à la classe ViewPager dans la bibliothèque de support Android. Quelles modifications ont été apscopes et comment est-il possible de “recomstackr” la bibliothèque avec le changement de ViewPager?

J’ai résolu ce problème très simplement en utilisant un petit hack dans l’adaptateur. Voici mon code:

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

Et puis, dans le ViewPager, nous mettons la page courante au centre:

 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 

Merci pour votre réponse Shereef.

Je l’ai résolu un peu différemment.

J’ai changé le code de la classe ViewPager de la bibliothèque de support Android. La méthode setCurrentItem(int)

change la page avec l’animation. Cette méthode appelle une méthode interne qui requirejs l’index et un indicateur permettant un défilement régulier. Ce drapeau est boolean smoothScroll . L’extension de cette méthode avec un deuxième paramètre boolean smoothScroll résolu pour moi. L’appel de cette méthode setCurrentItem(int index, boolean smoothScroll) m’a permis de le faire défiler indéfiniment.

Voici un exemple complet:

Veuillez considérer que seule la page centrale est affichée. De plus, je stockais les pages séparément, ce qui me permettait de les gérer plus facilement.

 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]); // resortingeve 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]); // resortingeve stuff and insert it to the right page insertStuff(pages[2]); } // go back to the center allowing to scroll indefinitely mDayPager.setCurrentItem(1, false); } } }); 

Cependant, sans Jon Willis Code, je ne l’aurais pas résolu moi-même.

EDIT: voici un article de blog à ce sujet:

Téléavertisseur à affichage infini en remplaçant 4 méthodes d’adaptateur dans votre classe d’adaptateur existante

  @Override public int getCount() { return Integer.MAX_VALUE; } @Override public CharSequence getPageTitle(int position) { Ssortingng 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); } 

Tout ce que vous avez à faire est de regarder l’exemple ici

Vous constaterez que dans la ligne 295, la page est toujours définie sur 1 de sorte qu’elle soit défilable et que le nombre de pages est 3 dans la méthode getCount() .

Ce sont les deux choses principales que vous devez changer, le rest est votre logique et vous pouvez les gérer différemment.

Il suffit de créer un compteur personnel qui compte la vraie page sur laquelle vous vous trouvez, car la position ne sera plus utilisable après avoir toujours réglé la page actuelle sur 1 sur la ligne 295.

ps ce code n’est pas le mien il a été référencé dans la question que vous avez liée dans votre question

En fait, j’ai examiné les différentes façons de faire cette pagination “infinie”, et même si la notion humaine du temps est infinie (même si nous avons une notion du début et de la fin des temps), les ordinateurs dans le discret. Il y a un temps minimum et maximum (qui peut être ajusté au fil du temps, rappelez-vous la base de la peur de l’an 2000?).

Quoi qu’il en soit, le but de cette discussion est qu’elle est / devrait être suffisante pour prendre en charge une plage de dates relativement infinie dans une plage de dates réellement définie. Un bon exemple de ceci est l’implémentation CalendarView du framework Android, et le WeeksAdapter en son sein. La date minimale par défaut est en 1900 et la date maximale par défaut est en 2100, cela devrait couvrir 99% de l’utilisation du calendrier de quiconque dans un rayon de 10 ans autour d’aujourd’hui facilement.

Ce qu’ils font dans leur mise en œuvre (axé sur les semaines) est de calculer le nombre de semaines entre la date minimale et la date maximale. Cela devient le nombre de pages dans le pager. Rappelez-vous que le pageur n’a pas besoin de maintenir toutes ces pages simultanément ( setOffscreenPageLimit(int) ), il suffit de pouvoir créer la page en fonction du numéro de page (ou de l’index / de la position). Dans ce cas, l’index est le nombre de semaines que la semaine est à partir de la date minimale. Avec cette approche, il vous suffit de conserver la date minimale et le nombre de pages (distance par rapport à la date maximale), puis, pour toute page, vous pouvez facilement calculer la semaine associée à cette page. Ne pas danser autour du fait que ViewPager ne supporte pas le bouclage (alias pagination infinie), et essaye de le forcer à se comporter comme il peut défiler à l’infini.

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

Puis WeekDaysFragment peut facilement afficher la semaine à partir de la date passée dans ses arguments.

Alternativement, il semble que certaines versions de l’application Calendrier sur Android utilisent un ViewSwitcher (ce qui signifie qu’il n’y a que 2 pages, celle que vous voyez et la page cachée). Il modifie ensuite l’animation de transition en fonction de la manière dont l’utilisateur a balayé et restitue la page suivante / précédente en conséquence. De cette façon, vous obtenez une pagination infinie car il suffit de basculer entre deux pages à l’infini. Cela nécessite toutefois l’utilisation d’une View pour la page, ce qui est la manière dont je suis allé avec la première approche.

En général, si vous voulez une “pagination infinie”, c’est probablement parce que vos pages sont basées sur des dates ou des heures. Si tel est le cas, envisagez d’utiliser un sous-ensemble de temps fini à la place. Voici comment CalendarView est implémenté par exemple. Ou vous pouvez utiliser l’approche de ViewSwitcher . L’avantage de ces deux approches est que rien de particulièrement inhabituel avec ViewSwitcher ou ViewPager , et ne nécessite aucune astuce ou réimplémentation pour les forcer à se comporter à l’infini ( ViewSwitcher est déjà conçu pour basculer entre les vues à l’infini, mais ViewPager est conçu pour travailler sur un ensemble de pages fini, mais pas nécessairement constant).

squelette adaptateur de curseur infini basé sur des échantillons précédents

Quelques problèmes critiques:

  • souvenez-vous de la position initiale (relative) dans la vue de page (balise utilisée dans l’échantillon), nous allons donc regarder cette position pour définir la position relative de la vue. sinon, l’ordre des enfants dans le téléavertisseur est mixte
  • doit remplir la première fois la vue absolue dans l’adaptateur. (le rest du temps, ce remplissage sera invalide) n’a trouvé aucun moyen de le forcer à remplir le gestionnaire de pagination. la vue absolue des temps de repos sera remplacée par le gestionnaire de pager avec des valeurs correctes.
  • lorsque les pages sont glissées rapidement, la page latérale (en fait à gauche) n’est pas remplie par le gestionnaire de pagination. aucune solution de contournement pour le moment, utilisez simplement la vue vide, elle sera remplie avec les valeurs réelles lorsque le glissement est arrêté. upd: solution rapide: désactivez la propriété destroyItem de l’adaptateur.

vous pouvez regarder le logcat pour comprendre ce qui se passe dans cet exemple

     

Et alors:

 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(Ssortingng.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", Ssortingng.format("position = %d", position)); return viewLayout; } @Override public void destroyItem(ViewGroup container, int position, Object object) { ((ViewPager) container).removeView((RelativeLayout) object); //Log.i("destroyItem", Ssortingng.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(Ssortingng.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(Ssortingng.format("relative: %d, global: %d", position, globalPosition)); Log.i("onPageSelected", Ssortingng.format("focusedPage/position = %d, globalPosition = %d", position, globalPosition)); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { //Log.i("onPageScrolled", Ssortingng.format("position = %d, positionOffset = %f", position, positionOffset)); } @Override public void onPageScrollStateChanged(int state) { Log.i("onPageScrollStateChanged", Ssortingng.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); } } 

C’est piraté par 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 :

     

Terminé

Pour un défilement infini avec des jours, il est important que vous ayez le bon fragment dans le pager. J’ai donc écrit ma réponse sur cette page ( Viewpager dans Android pour basculer entre les jours sans fin )

Ça marche très bien! Les réponses ci-dessus ne fonctionnaient pas pour moi car je voulais que cela fonctionne.

J’ai construit une bibliothèque qui peut faire n’importe quel ViewPager, pagerAdapter (ou FragmentStatePagerAdapter), et TabLayout facultatif défilant à l’infini.

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