Cómo migrar contenido de ViewPager a ViewPager2

ViewPager2 es una versión mejorada de la biblioteca ViewPager que ofrece una funcionalidad mejorada y aborda las dificultades comunes que se presentan cuando se usa ViewPager. Si tu app ya usa ViewPager, lee esta página para obtener más información sobre cómo migrar a ViewPager2.

Si quieres usar ViewPager2 en tu app y, actualmente, no usas ViewPager, lee Cómo deslizar entre fragmentos con ViewPager2 y Cómo crear vistas deslizantes con pestañas usando 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 para el desarrollo, mientras que ViewPager no. Sin embargo, ViewPager2 también ofrece varias otras ventajas específicas.

Compatibilidad con orientación vertical

ViewPager2 admite la paginación vertical, además de la horizontal tradicional. Puedes habilitar la paginación vertical para un elemento ViewPager2 configurando su 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 configurar este atributo de manera 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 derecha a izquierda se habilita automáticamente cuando corresponde según la configuración regional, pero también puedes habilitarla de forma manual para un elemento ViewPager2 configurando su 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 configurar este atributo de manera 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, llamando a notifyDatasetChanged() para actualizar la IU cuando cambia la colección subyacente.

Esto significa que tu app puede modificar de forma dinámica la colección de fragmentos en el tiempo de ejecución, y ViewPager2 mostrará correctamente la colección modificada.

DiffUtil

ViewPager2 se basa en RecyclerView, lo que significa que tiene acceso a la clase de utilidad DiffUtil. Esto genera varios beneficios, pero, sobre todo, 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 de tus archivos de diseño XML por 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, tenías que extender la clase de adaptador que proporcionaba páginas nuevas al objeto. Según el caso de uso, ViewPager usó tres clases abstractas diferentes. ViewPager2 solo usa dos clases abstractas.

Para 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 de constructor. Cuando extiendes FragmentStateAdapter para una clase de adaptador ViewPager2, tienes las siguientes opciones para los parámetros del constructor:

  • Es el objeto FragmentActivity o Fragment en el que 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

Tus clases de adaptador también deben anular métodos diferentes para ViewPager2 que los que anularon para ViewPager:

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

Resumen

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

  1. Cambia la superclase a RecyclerView.Adapter para paginar las vistas o a FragmentStateAdapter para paginar los 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 las 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 para mostrar pestañas horizontales de navegación, debes refactorizar el objeto TabLayout para integrarlo con ViewPager2.

TabLayout se desvinculó de ViewPager2 y ahora está disponible como parte de los componentes de Material. Esto significa que, para usarla, debes agregar la dependencia adecuada a tu archivo build.gradle:

Groovy

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

Kotlin

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

También debes cambiar la ubicación del elemento TabLayout en la jerarquía de tu 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 adjunta el objeto TabLayout al objeto ViewPager. Si bien TabLayout usa su propio método setupWithViewPager() para integrarse en ViewPager, requiere una instancia de TabLayoutMediator para integrarse en ViewPager2.

El objeto TabLayoutMediator también controla la tarea de generar títulos de la 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 de forma nativa vistas de desplazamiento anidadas en los casos en que la vista de desplazamiento tiene la misma orientación que el objeto ViewPager2 que la contiene. Por ejemplo, el desplazamiento no funcionaría para 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 esperes desplazar el elemento anidado. En la muestra de desplazamiento anidado de ViewPager2, se indica una forma de resolver este problema con un diseño de wrapper personalizado versátil.

Recursos adicionales

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

Videos