ViewPager 2 - nouvelle fonctionnalité dans l'ancien wrapper

ViewPager est l'un des composants les plus connus et les plus utilisés de la bibliothèque de support Android. Tous les carrousels, onboardings et curseurs les plus simples y sont réalisés. En février 2019, l'équipe de développement AndroidX a publié ViewPager2. Voyons quelles étaient ces conditions préalables et quels sont les avantages de la version mise à jour du composant.



ViewPager 2


Au moment de la rédaction de l'article (juillet 2019), une version bêta de ViewPager2 est disponible , ce qui signifie que les problèmes mentionnés ci-dessous peuvent être corrigés et les fonctionnalités améliorées et étendues. Les développeurs promettent à l'avenir d'ajouter la prise en charge de TabLayout (alors qu'il ne peut fonctionner qu'avec la première version), d'optimiser les performances de l'adaptateur, d'apporter de nombreuses corrections mineures et de finaliser la documentation.

Intégration


Le composant n'est pas fourni avec des packages standard, mais est connecté séparément. Pour ce faire, ajoutez la ligne suivante au bloc des dépendances dans le script gradle de votre module:

implementation "androidx.viewpager2:viewpager2:1.0.0-beta02" 

Implémentation


Commençons par la bonne nouvelle: passer de la première à la deuxième version est aussi simple que possible et se résume à un changement dans les importations. La bonne vieille syntaxe n'a pas été touchée: la méthode getCurrentItem () renvoie la page actuelle, ViewPager2.onPageChangeCallback vous permet de vous abonner à l' état de pageur, l'adaptateur est toujours installé via setAdapter ().



Il vaut la peine de creuser plus profondément, car il devient clair que les premier et second pagers n'ont rien en commun, sauf des interfaces. La connaissance de l'implémentation de la méthode setAdapter () ne laisse aucun doute:

 public final void setAdapter(@Nullable Adapter adapter) { mRecyclerView.setAdapter(adapter); } 

Oui, ViewPager2 est juste un wrapper sur RecyclerView . D'une part, c'est un gros plus, d'autre part - cela ajoute un mal de tête. Déguiser RecyclerView sous forme de brochure est devenu possible avec l'avènement de PagerSnapHelper . Cette classe modifie la physique du parchemin. Lorsque l'utilisateur relâche son doigt, PagerSnapHelper calcule l'élément de liste le plus proche de la ligne centrale de la liste et, avec une animation fluide, l'aligne exactement au centre. Ainsi, si le balayage était suffisamment net, la liste défile jusqu'à l'élément suivant, sinon - avec l'animation revient à son état d'origine.

 new PagerSnapHelper().attachToRecyclerView(mRecyclerView); 

image
Lorsque vous utilisez PagerSnapHelper, assurez-vous que la largeur et la hauteur du RecyclerView lui-même, ainsi que tous ses ViewHolders, sont définis sur MATCH_PARENT. Sinon, le comportement de SnapHelper sera imprévisible, des bugs peuvent survenir dans des endroits complètement inattendus. Tout cela rend la création d'un carrousel d'éléments de petite hauteur assez longue, bien que possible.

Compte tenu de tout ce qui précède, dans la disposition, le widget ressemblera à ceci:

 <androidx.viewpager2.widget.ViewPager2 android:id="@+id/main_pager" android:layout_width="match_parent" android:layout_height="match_parent" /> 

Dans le même package que ViewPager2, nous pouvons également trouver la classe ScrollEventAdapter , qui aide à maintenir la continuité de la syntaxe. ScrollEventAdapter implémente RecyclerView.OnScrollListener et transforme les événements de défilement en événements OnPageChangeCallback .

 @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { if (mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG && newState == RecyclerView.SCROLL_STATE_DRAGGING) { ... dispatchStateChanged(SCROLL_STATE_DRAGGING); return; } ... } 

Maintenant, OnPageChangeCallback n'est pas représenté par une interface, mais par une classe abstraite, qui vous permet de redéfinir uniquement les méthodes nécessaires (dans la plupart des cas, vous n'avez besoin que de o nPageSelected (Int) , qui fonctionne lorsqu'une page spécifique est sélectionnée):

 main_pager.registerOnPageChangeCallback( object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { //do your stuff } } ) 

CARACTÉRISTIQUES


Il convient de noter la méthode setPageTransformer () , qui prend ViewPager2.PageTransformer comme paramètre. Il définit un rappel pour chaque événement de sélection de page et sert à définir sa propre animation pour cette page. Le rappel reçoit la vue de la page en cours et son numéro en entrée. L'analogue le plus proche de cette méthode est l' ItemAnimator de RecyclerView .

Dans les nouvelles versions de la bibliothèque, deux implémentations du transformateur ont été ajoutées:

CompositePageTransformer et MarginPageTransformer . Le premier est responsable de la combinaison des transformateurs afin d'appliquer plusieurs transformations à un pager à la fois, et le second de l'indentation entre les pages:

image

De plus, le nouveau widget prend en charge les changements d'orientation: en appelant simplement la méthode setOrientation () , vous pouvez transformer votre pager en liste verticale avec des balayages de haut en bas:

 main_pager.setOrientation(ViewPager2.ORIENTATION_VERTICAL) 

Cela se produit à nouveau grâce à la transition vers RecyclerView : sous le capot, un changement d'orientation du LayoutManager est appelé , qui est responsable de l'affichage des éléments de la liste. Il convient de noter que la délégation d'un grand nombre de tâches à d'autres classes a profité au nouveau composant: sa liste est devenue beaucoup plus compacte et lisible.

Ce n'est pas la fin du plaisir. Dans une mise à jour, ViewPager2 a reçu la prise en charge d' ItemDecoration : une classe d' assistance pour décorer la vue enfant. Ce mécanisme peut être utilisé pour dessiner des séparateurs entre les éléments, les bordures, la surbrillance des cellules.

Il existe déjà de nombreuses implémentations prêtes à l'emploi de décorateurs, car pendant de nombreuses années, ils ont été utilisés avec succès lorsque vous travaillez avec le RecyclerView habituel. Tous les développements sont désormais applicables aux téléavertisseurs. Prêt à l'emploi, une implémentation standard des séparateurs de téléavertisseurs est disponible:

 main_pager.addItemDecoration( DividerItemDecoration(this, RecyclerView.HORIZONTAL) ) 

Avec la prochaine mise à jour en mai 2019, ViewPager2 a ajouté une autre méthode importante: setOffscreenPageLimit (Int) . Il est responsable du nombre d'éléments à droite et à gauche de la centrale qui seront initialisés dans le pager. Bien que RecyclerView soit responsable de la mise en cache et de l'affichage de la vue par défaut, cette méthode vous permet de définir explicitement le nombre souhaité d'éléments à charger.

Adaptateur


Le successeur idéologique du premier adaptateur de pageur est le FragmentStateAdapter : les interfaces d'interaction et la dénomination des classes sont presque les mêmes. Les modifications ont affecté uniquement la dénomination de certaines méthodes. Si auparavant, il était nécessaire d'implémenter la fonction abstraite getItem (position) afin de renvoyer l'instance Fragment souhaitée pour une position donnée, et cette dénomination pourrait être interprétée de deux manières, maintenant cette fonction a été renommée en createFragment (position) . Le nombre total de fragments est fourni comme précédemment par la fonction getCount () .

Parmi les modifications structurelles importantes apportées à l'interface, il convient également de noter que l'adaptateur a désormais la capacité de contrôler le cycle de vie de ses éléments.Par conséquent, avec le FragmentManager dans le constructeur, il accepte un objet Lifecycle , soit une activité soit un fragment . Pour cette raison, pour des raisons de sécurité, les méthodes saveState () et restoreState () ont été déclarées finales et fermées pour héritage.
La classe FragmentViewHolder est responsable du stockage des fragments dans RecyclerView . La méthode onCreateViewHolder () de FragmentStateAdapter appelle FragmentViewHolder.create () .

 static FragmentViewHolder create(ViewGroup parent) { FrameLayout container = new FrameLayout(parent.getContext()); container.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ); container.setId(ViewCompat.generateViewId()); container.setSaveEnabled(false); return new FragmentViewHolder(container); } 

Lorsque la méthode onBindViewHolder () est appelée , l'identifiant de l'élément à la position actuelle et l'identifiant ViewHolder sont associés , pour y attacher davantage le fragment:

 final long itemId = holder.getItemId(); final int viewHolderId = holder.getContainer().getId(); final Long boundItemId = itemForViewHolder(viewHolderId); ... mItemIdToViewHolder.put(itemId, viewHolderId); ensureFragment(position); //   

Et enfin, lorsque vous attachez un conteneur du ViewHolder à la hiérarchie View , une FragmentTransaction est exécutée, en ajoutant un Fragment au conteneur:

 void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) { Fragment fragment = mFragments.get(holder.getItemId()); ... scheduleViewAttach(fragment, container); mFragmentManager.beginTransaction() .add(fragment, "f" + holder.getItemId()) .setMaxLifecycle(fragment, STARTED) .commitNow(); ... } 

Ainsi, deux utilisations de ViewPager2 émergent : en héritant la classe d'adaptateur, soit directement à partir de RecyclerView.Adapter ou de FragmentStateAdapter .



Vous aurez sûrement une question: pourquoi utiliser un deuxième téléavertisseur avec des fragments et un adaptateur pour eux alors qu'il existe une première version fonctionnant normalement? ViewPager est loin d'être une « solution miracle» lorsque vous travaillez avec de grandes listes de données dynamiques. Il est idéal pour créer des carrousels avec un ensemble statique d'images ou de bannières, mais les flux d'actualités paginés avec chargement des messages publicitaires, le filtrage donnent naissance à des monstres durs et laids. Tôt ou tard, vous rencontrerez sûrement un désir ardent de tout réécrire sur RecyclerView . Maintenant, vous n’avez pas à le faire, car le pager lui-même s’est transformé en lui, empruntant ses puissantes capacités pour travailler avec des listes dynamiques, tout en les encapsulant dans la syntaxe habituelle.

La seule chose que PagerAdapter peut nous offrir est la méthode notifyDataSetChanged () , qui force le ViewPager à redessiner tous les éléments de liste rendus. Vous pouvez raisonnablement remarquer que personne ne nous empêche de stocker une liste de positions pour les éléments existants et de retourner POSITION_UNCHANGED à partir de la méthode getItemPosition () pour eux, c'est tout. Cependant, cette solution ne peut pas être qualifiée de belle, elle est plutôt lourde, de plus, il est difficile d'étendre ces cas lorsque les éléments de la liste changent constamment et ne sont pas seulement ajoutés séquentiellement à la fin. FragmentStateAdapter dispose d'un arsenal complet de méthodes RecyclerView.Adapter , de sorte que la logique de redessiner les éléments peut être configurée de manière beaucoup plus flexible. De plus, avec FragmentStateAdapter, vous pouvez utiliser DiffUtil , qui vous permet d'automatiser presque complètement le travail de notification des modifications.


Attention! Pour que les méthodes notify ... fonctionnent correctement (à l'exception de notifyDataSetChanged ), les méthodes getItemId (Int) et c ontainsItem (Long) doivent être redéfinies. Cela est dû au fait que l'implémentation par défaut ne regarde que le numéro de page et si, par exemple, vous ajoutez un nouvel élément après celui en cours, il ne sera pas ajouté, car getItemId restera inchangé. Un exemple de substitution de ces deux méthodes basé sur une liste d'éléments de type Int :

 override fun getItemId(position: Int): Long { return items[position].toLong() } override fun containsItem(itemId: Long): Boolean { return items.contains(itemId.toInt()) } 



La principale raison de l'apparition de ViewPager2 est la réticence à réinventer la roue. D'une part, l' équipe de développement AndroidX est clairement prête à abandonner maintenant le ViewPager obsolète et n'investira certainement pas dans l'extension de ses fonctionnalités. Oui et pourquoi? Après tout, RecyclerView sait déjà tout ce qui est nécessaire. D'un autre côté, la suppression et la suppression du support pour un composant aussi largement utilisé n'ajouteront évidemment pas la fidélité de la communauté.

Pour résumer: ViewPager2 mérite certainement l'attention, même si pour le moment il n'est pas sans défauts graves.

Inconvénients


  • Humidité et un grand nombre de bugs (excusable pour la version bêta);
  • La proximité. RecyclerView est un domaine privé de ViewPager2 , ce qui nous prive de nombreuses opportunités: il est impossible d'implémenter un glisser- déposer ou un glisser-déposer ( ItemTouchHelper se connecte directement à RecyclerView ), vous ne pouvez pas redéfinir ItemAnimator de quelque manière que ce soit, n'accédez pas directement à LayoutManager et n'utilisez pas RecycledViewPool . Cependant, avec la sortie de nouvelles versions du composant, le nombre de méthodes d'interface héritées de RecyclerView augmente (par exemple, ItemDecoration ), et nous pouvons espérer ajouter les méthodes manquantes à l'avenir.

Avantages


  • Prise en charge de tous les avantages de RecyclerView.Adapter : combinaison d'éléments de différents types dans une liste, ajout et suppression d'éléments directement pendant le balayage, redessinage animé du contenu de la liste lors du changement;
  • Prise en charge de toute la gamme des méthodes de notification et calcul automatique des modifications à l'aide de DiffUtil ;
  • Facilité de transition due à la continuité de la syntaxe;
  • Support pour l'orientation verticale et horizontale "hors de la boîte";
  • Prise en charge RTL ;
  • Support ItemDecorator ;
  • Prise en charge du logiciel faisant défiler fakeScrollBy () ;
  • Possibilité de définir manuellement le nombre d'articles chargés;
  • La possibilité d'utiliser n'importe laquelle des solutions open source prêtes à l'emploi pour réduire le code passe-partout , ce qui est inévitable lors de l'écriture de RecyclerView.Adapter personnalisé. Par exemple, EasyAdapter .

En résumé, je tiens à dire que ViewPager2 vaut vraiment la peine d'être examiné de plus près. Il s'agit d'une solution prometteuse, extensible et fonctionnelle. Et bien qu'il soit trop tôt pour lancer un nouveau widget en production, il est sûr de dire qu'après une version complète, il peut et doit complètement supplanter son ancêtre.

Pour les audacieux et décisifs que l'article a inspiré à expérimenter, PagerSnapHelper est apparu dans la 28e version de la bibliothèque de support , ce qui signifie que vous pouvez l'utiliser avec votre RecyclerView en créant vous-même ViewPager2 .

L'exemple d' opération de ViewPager2 et FragmentStateAdapter .

Notes de version officielles ViewPager2

Source: https://habr.com/ru/post/fr461873/


All Articles