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

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

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