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, poursuivez votre lecture pour en savoir plus au sujet de la migration vers ViewPager2.

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

Avantages de la migration vers ViewPager2

La principale raison de migrer est que ViewPager2 bénéficie d'une assistance au développement active, contrairement à ViewPager. Toutefois, 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 de manière programmatique à l'aide de la méthode setOrientation().

Compatibilité avec la mise en page de droite à gauche

ViewPager2 est compatible avec la pagination de droite à gauche. La pagination de droite à gauche est activée automatiquement, le cas échéant, en fonction des paramètres régionaux. Toutefois, 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 de manière programmatique à l'aide de la méthode setLayoutDirection().

Collections de fragments modifiables

ViewPager2 permet de paginer une collection modifiable de fragments, en appelant notifyDatasetChanged() pour mettre à jour l'UI 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 affichera 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 surtout, cela signifie que les objets ViewPager2 profitent de manière native des animations de modification de l'ensemble de données de la classe RecyclerView.

Migrer votre application vers ViewPager2

Pour remplacer les objets ViewPager par ViewPager2 dans votre application, procédez comme suit :

Mettre à jour les fichiers de mise en page XML

Tout d'abord, remplacez les éléments ViewPager de 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 deviez étendre la classe d'adaptateur qui fournissait de 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 d'adaptateur pour étendre la classe abstraite appropriée comme suit :

Paramètres du constructeur

Les classes d'adaptateur basées sur des fragments qui héritent de FragmentPagerAdapter ou FragmentStatePagerAdapter acceptent toujours un seul objet FragmentManager comme paramètre de 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 Fragment dans lequel réside 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'adaptateur basées sur des vues qui héritent directement de RecyclerView.Adapter ne nécessitent pas de paramètre de constructeur.

Ignorer les méthodes

Vos classes d'adaptateur doivent également remplacer différentes méthodes pour ViewPager2 par rapport à ViewPager :

  • Au lieu de getCount(), remplacez getItemCount(). À part le nom, cette méthode reste inchangée.
  • Au lieu de getItem(), remplacez createFragment() dans les classes d'adaptateur 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 afin de l'utiliser avec ViewPager2, vous devez apporter les modifications suivantes :

  1. Remplacez la super-classe par RecyclerView.Adapter pour paginer les vues ou par FragmentStateAdapter pour paginer les fragments.
  2. Modifiez les paramètres du constructeur dans les classes d'adaptateur basées sur des fragments.
  3. Remplacez getItemCount() au lieu de getCount().
  4. Remplacement de createFragment() au lieu de getItem() dans les classes d'adaptateur 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 introduit des modifications dans 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é en tant qu'enfant de l'élément ViewPager. En revanche, 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 des titres de la 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éroulants imbriqués

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

Pour prendre en charge une vue avec défilement dans un objet ViewPager2 ayant la même orientation, vous devez appeler requestDisallowInterceptTouchEvent() sur l'objet ViewPager2 lorsque vous souhaitez faire défiler l'élément imbriqué. L'exemple de défilement imbriqué ViewPager2 montre comment résoudre ce problème à l'aide d'une mise en page de wrapper personnalisée polyvalente.

Ressources supplémentaires

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

Vidéos