Cómo migrar contenido de ViewPager a ViewPager2

ViewPager2 es una versión mejorada de la biblioteca de ViewPager, que ofrece una funcionalidad mejorada y aborda dificultades comunes con el uso de ViewPager. Si tu app ya usa ViewPager, lee esta página para obtener más información sobre la migración a ViewPager2.

Si quieres usar ViewPager2 en tu app y actualmente no usas ViewPager, lee Cómo deslizarse entre fragmentos con ViewPager2 y Cómo crear vistas deslizables con pestañas con ViewPager2 para obtener más información.

Beneficios de la migración a ViewPager2

El motivo principal para migrar es que ViewPager2 recibe asistencia activa de desarrollo y ViewPager no. Sin embargo, ViewPager2 también ofrece varias ventajas específicas.

Compatibilidad con orientación vertical

ViewPager2 admite la paginación vertical, además de la paginación horizontal tradicional. Puedes habilitar la paginación vertical de un elemento ViewPager2 si configuras el atributo android:orientation:

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

También puedes establecer el atributo de forma programática con el método setOrientation().

Asistencia de derecha a izquierda

ViewPager2 admite la paginación de derecha a izquierda (RTL). La paginación de RTL se habilita automáticamente cuando corresponde según la configuración regional, pero también puedes habilitar manualmente la paginación de RTL de un elemento ViewPager2 si configuras el atributo android:layoutDirection:

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

También puedes establecer el atributo de forma programática con el método setLayoutDirection().

Colecciones de fragmentos modificables

ViewPager2 admite la paginación a través de una colección modificable de fragmentos, con llamadas a notifyDatasetChanged() para actualizar la IU cuando cambia la colección subyacente.

Esto significa que la app puede modificar dinámicamente la colección de fragmentos durante el tiempo de ejecución y ViewPager2 mostrará correctamente la colección modificada.

DiffUtil

ViewPager2 se compila sobre RecyclerView, lo que significa que tiene acceso a la clase de utilidad DiffUtil. Esto tiene varios beneficios, pero, en particular, significa que los objetos ViewPager2 aprovechan de forma nativa las animaciones de cambio de conjunto de datos de la clase RecyclerView.

Cómo migrar tu app a ViewPager2

Sigue estos pasos para actualizar los objetos ViewPager en tu app a ViewPager2:

Cómo actualizar los archivos de diseño XML

Primero, reemplaza los elementos ViewPager en los archivos de diseño XML con elementos 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" />
    

Cómo actualizar las clases de adaptador

Cuando usabas ViewPager, debías extender la clase de adaptador que proporcionaba páginas nuevas al objeto. Según el caso práctico, ViewPager usaba tres clases abstractas diferentes. ViewPager2 solo usa dos clases abstractas.

Por cada objeto ViewPager que conviertas en un objeto ViewPager2, actualiza la clase de adaptador para extender la clase abstracta adecuada de la siguiente manera:

Parámetros de constructor

Las clases de adaptador basadas en fragmentos que heredan de FragmentPagerAdapter o FragmentStatePagerAdapter siempre aceptan un solo objeto FragmentManager como parámetro constructor. Cuando extiendas FragmentStateAdapter para una clase de adaptador ViewPager2, tendrás las siguientes opciones de parámetros de constructor:

  • El objeto FragmentActivity o el objeto Fragment donde reside el objeto ViewPager2. Por lo general, esta es la mejor opción.
  • Un objeto FragmentManager y un objeto Lifecycle.

Las clases de adaptador basadas en vistas que heredan directamente de RecyclerView.Adapter no requieren un parámetro de constructor.

Anular métodos

Las clases de adaptador también deben anular métodos diferentes en ViewPager2, al igual que en ViewPager:

  • En lugar de getCount(), anula getItemCount(). Aparte del nombre, este método no se modificó.
  • En lugar de getItem(), anula createFragment() en las clases de adaptadores basadas en fragmentos. Asegúrate de que el método createFragment() nuevo proporcione siempre una instancia de fragmento nueva cada vez que se llame a la función en lugar de reutilizar instancias.

Resumen

En resumen, a fin de convertir una clase de adaptador ViewPager para usarla con ViewPager2, debes realizar los siguientes cambios:

  1. Cambia la superclase a RecyclerView.Adapter para paginar vistas o FragmentStateAdapter para paginar fragmentos.
  2. Cambia los parámetros del constructor en clases de adaptador basadas en fragmentos.
  3. Anula getItemCount() en lugar de getCount().
  4. Anula createFragment() en lugar de getItem() en clases de adaptador basadas en fragmentos.

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

Cómo refactorizar interfaces de TabLayout

ViewPager2 trae cambios en la integración de TabLayout. Si actualmente usas un ViewPager con un objeto TabLayout a fin de mostrar pestañas horizontales en la navegación, debes refactorizar el objeto TabLayout para la integración con ViewPager2.

TabLayout se desacopló de ViewPager2 y ahora está disponible como parte de Componentes de material. Esto significa que, para usarlo, debes agregar la dependencia adecuada al archivo build.gradle:

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

También debes cambiar la ubicación del elemento TabLayout en la jerarquía del archivo de diseño XML. Con ViewPager, el elemento TabLayout se declara como un elemento secundario del elemento ViewPager; pero con ViewPager2, el elemento TabLayout se declara directamente sobre el elemento ViewPager2, en el mismo nivel:

<!-- 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>
    

Por último, debes actualizar el código que conecta el objeto TabLayout con el objeto ViewPager. Si bien TabLayout usa un método setupWithViewPager() propio para integrarse con ViewPager, requiere una instancia TabLayoutMediator a fin de integrarse con ViewPager2.

El objeto TabLayoutMediator también controla la tarea de generar títulos de página para el objeto TabLayout, lo que significa que la clase de adaptador no necesita anular 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();
        }
        ...
    }
    

Cómo admitir elementos desplazables anidados

ViewPager2 no admite vistas de desplazamiento anidadas de forma nativa cuando la vista de desplazamiento tiene la misma orientación que el objeto ViewPager2 que la contiene. Por ejemplo, el desplazamiento no funcionaría en una vista de desplazamiento vertical dentro de un objeto ViewPager2 orientado verticalmente.

Para admitir una vista de desplazamiento dentro de un objeto ViewPager2 con la misma orientación, debes llamar a requestDisallowInterceptTouchEvent() en el objeto ViewPager2 cuando quieras desplazarte por el elemento anidado. El ejemplo de desplazamiento anidado de ViewPager2 presenta una forma de resolver este problema con un diseño de wrapper personalizado y versátil.

Recursos adicionales

Para obtener más información sobre ViewPager2, consulta los siguientes recursos adicionales.

Ejemplos

Videos