Migrer de ViewPager vers ViewPager2

ViewPager2 est une version améliorée de la bibliothèque ViewPager qui offre des fonctionnalités améliorées et résout les problèmes courants liés à l'utilisation de ViewPager. Si votre application utilise déjà ViewPager, consultez cette page pour en savoir plus sur la migration vers ViewPager2.

Si vous souhaitez utiliser ViewPager2 dans votre application et que vous n'utilisez pas actuellement ViewPager, consultez Faire glisser des fragments à l'aide de ViewPager2 et Créer des vues à balayer avec des onglets à l'aide de ViewPager2 pour en savoir plus.

Avantages de la migration vers ViewPager2

La principale raison de la migration est que ViewPager2 bénéficie d'une assistance de développement active, contrairement à ViewPager. Cependant, ViewPager2 offre également plusieurs autres avantages spécifiques.

Prise en charge de l'orientation verticale

ViewPager2 est compatible avec la pagination verticale en plus de la pagination horizontale traditionnelle. Vous pouvez activer la pagination verticale pour un élément ViewPager2 en définissant son attribut android:orientation:

<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:orientation="vertical" />

Vous pouvez également définir cet attribut par programmation à l'aide de la méthode setOrientation().

Soutien de droite à gauche

ViewPager2 est compatible avec la pagination de droite à gauche. La pagination de droite à gauche est activée automatiquement en fonction des paramètres régionaux, mais vous pouvez également l'activer manuellement pour un élément ViewPager2 en définissant son attribut android:layoutDirection:

<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layoutDirection="rtl" />

Vous pouvez également définir cet attribut par programmation à l'aide de la méthode setLayoutDirection().

Collections de fragments modifiables

ViewPager2 permet la pagination via une collection de fragments modifiable, en appelant notifyDatasetChanged() pour mettre à jour l'interface utilisateur lorsque la collection sous-jacente change.

Cela signifie que votre application peut modifier de manière dynamique la collection de fragments au moment de l'exécution, et que ViewPager2 affiche correctement la collection modifiée.

DiffUtil

ViewPager2 est basé sur RecyclerView, ce qui signifie qu'il a accès à la classe utilitaire DiffUtil. Cela présente plusieurs avantages, mais cela signifie notamment que les objets ViewPager2 exploitent de manière native les animations de modification de l'ensemble de données de la classe RecyclerView.

Migrer votre application vers ViewPager2

Pour mettre à jour les objets ViewPager de votre application vers ViewPager2, procédez comme suit:

Mettre à jour des fichiers de mise en page XML

Tout d'abord, remplacez les éléments ViewPager dans vos fichiers de mise en page XML par des éléments ViewPager2:

<!-- A ViewPager element -->
<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<!-- A ViewPager2 element -->
<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Mettre à jour les classes d'adaptateur

Lorsque vous utilisiez ViewPager, vous avez dû étendre la classe d'adaptateur qui fournissait les nouvelles pages à l'objet. Selon le cas d'utilisation, ViewPager a utilisé trois classes abstraites différentes. ViewPager2 n'utilise que deux classes abstraites.

Pour chaque objet ViewPager que vous convertissez en objet ViewPager2, mettez à jour la classe de l'adaptateur pour étendre la classe abstraite appropriée, comme suit:

Paramètres du constructeur

Les classes d'adaptateurs basées sur des fragments qui héritent de FragmentPagerAdapter ou FragmentStatePagerAdapter acceptent toujours un seul objet FragmentManager en tant que paramètre constructeur. Lorsque vous étendez FragmentStateAdapter pour une classe d'adaptateur ViewPager2, vous disposez des options suivantes pour les paramètres de constructeur:

  • Objet FragmentActivity ou objet Fragment où se trouve l'objet ViewPager2. Dans la plupart des cas, il s'agit de la meilleure option.
  • Un objet FragmentManager et un objet Lifecycle

Les classes d'adaptateurs basées sur les vues qui héritent directement de RecyclerView.Adapter ne nécessitent pas de paramètre constructeur.

Ignorer les méthodes

Vos classes d'adaptateur doivent également remplacer des méthodes pour ViewPager2 et ViewPager:

  • Au lieu de getCount(), remplacez getItemCount(). Hormis le nom, cette méthode reste inchangée.
  • Au lieu de getItem(), remplacez createFragment() dans les classes d'adaptateurs basées sur des fragments. Assurez-vous que votre nouvelle méthode createFragment() fournit toujours une nouvelle instance de fragment chaque fois que la fonction est appelée au lieu de réutiliser des instances.

Résumé

En résumé, pour convertir une classe d'adaptateur ViewPager à utiliser avec ViewPager2, vous devez apporter les modifications suivantes:

  1. Remplacez la super-classe par RecyclerView.Adapter pour la pagination via les vues ou par FragmentStateAdapter pour la pagination via des fragments.
  2. Modifiez les paramètres du constructeur dans les classes d'adaptateur basées sur des fragments.
  3. Ignorez getItemCount() au lieu de getCount().
  4. Remplacement de createFragment() au lieu de getItem() dans les classes d'adaptateurs basées sur des fragments.

Kotlin

// A simple ViewPager adapter class for paging through fragments
class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
    override fun getCount(): Int = NUM_PAGES

    override fun getItem(position: Int): Fragment = ScreenSlidePageFragment()
}

// An equivalent ViewPager2 adapter class
class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
    override fun getItemCount(): Int = NUM_PAGES

    override fun createFragment(position: Int): Fragment = ScreenSlidePageFragment()
}

Java

// A simple ViewPager adapter class for paging through fragments
public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
    public ScreenSlidePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return new ScreenSlidePageFragment();
    }

    @Override
    public int getCount() {
        return NUM_PAGES;
    }
}

// An equivalent ViewPager2 adapter class
private class ScreenSlidePagerAdapter extends FragmentStateAdapter {
    public ScreenSlidePagerAdapter(FragmentActivity fa) {
        super(fa);
    }

    @Override
    public Fragment createFragment(int position) {
        return new ScreenSlidePageFragment();
    }

    @Override
    public int getItemCount() {
        return NUM_PAGES;
    }
}

Refactoriser les interfaces TabLayout

ViewPager2 apporte des modifications à l'intégration de TabLayout. Si vous utilisez actuellement un ViewPager avec un objet TabLayout pour afficher des onglets horizontaux pour la navigation, vous devez refactoriser l'objet TabLayout pour l'intégrer à ViewPager2.

TabLayout a été dissocié de ViewPager2 et est désormais disponible dans les composants Material. Cela signifie que pour l'utiliser, vous devez ajouter la dépendance appropriée à votre fichier build.gradle:

Groovy

implementation "com.google.android.material:material:1.1.0-beta01"

Kotlin

implementation("com.google.android.material:material:1.1.0-beta01")

Vous devez également modifier l'emplacement de l'élément TabLayout dans la hiérarchie de votre fichier de mise en page XML. Avec ViewPager, l'élément TabLayout est déclaré comme enfant de l'élément ViewPager. Avec ViewPager2, l'élément TabLayout est déclaré directement au-dessus de l'élément ViewPager2, au même niveau:

<!-- A ViewPager element with a TabLayout -->
<androidx.viewpager.widget.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</androidx.viewpager.widget.ViewPager>

<!-- A ViewPager2 element with a TabLayout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

Enfin, vous devez mettre à jour le code qui associe l'objet TabLayout à l'objet ViewPager. Bien que TabLayout utilise sa propre méthode setupWithViewPager() pour s'intégrer à ViewPager, il nécessite une instance TabLayoutMediator pour s'intégrer à ViewPager2.

L'objet TabLayoutMediator gère également la tâche de génération de titres de page pour l'objet TabLayout, ce qui signifie que la classe d'adaptateur n'a pas besoin de remplacer getPageTitle():

Kotlin

// Integrating TabLayout with ViewPager
class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        tabLayout.setupWithViewPager(viewPager)
    }
    ...
}

class DemoCollectionPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {

    override fun getCount(): Int  = 4

    override fun getPageTitle(position: Int): CharSequence {
        return "OBJECT ${(position + 1)}"
    }
    ...
}

// Integrating TabLayout with ViewPager2
class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = "OBJECT ${(position + 1)}"
        }.attach()
    }
    ...
}

Java

// Integrating TabLayout with ViewPager
public class CollectionDemoFragment extends Fragment {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        TabLayout tabLayout = view.findViewById(R.id.tab_layout);
        tabLayout.setupWithViewPager(viewPager);
    }
    ...
}

public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
    ...
    @Override
    public int getCount() {
        return 4;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "OBJECT " + (position + 1);
    }
    ...
}

// Integrating TabLayout with ViewPager2
public class CollectionDemoFragment : Fragment() {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        TabLayout tabLayout = view.findViewById(R.id.tab_layout);
        new TabLayoutMediator(tabLayout, viewPager,
                (tab, position) -> tab.setText("OBJECT " + (position + 1))
        ).attach();
    }
    ...
}

Prendre en charge les éléments à défilement imbriqués

ViewPager2 n'est pas compatible de manière native avec les vues de défilement imbriquées lorsque celle-ci a la même orientation que l'objet ViewPager2 qui la contient. Par exemple, le défilement ne fonctionne pas pour une vue à défilement vertical dans un objet ViewPager2 orienté verticalement.

Pour accepter une vue de défilement dans un objet ViewPager2 avec la même orientation, vous devez appeler requestDisallowInterceptTouchEvent() sur l'objet ViewPager2 si vous prévoyez de faire défiler l'élément imbriqué à la place. L'exemple de défilement imbriqué ViewPager2 illustre une façon de résoudre ce problème avec une mise en page de wrapper personnalisée polyvalente.

Ressources supplémentaires

Pour en savoir plus sur ViewPager2, consultez les ressources supplémentaires suivantes.

Exemples

Vidéos