Cómo migrar contenido de ViewPager a ViewPager2

ViewPager2 es una versión mejorada de la biblioteca de ViewPager que ofrece funciones mejoradas 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 deseas usar ViewPager2 en tu app y actualmente no usas ViewPager, lee Cómo deslizarse entre fragmentos con ViewPager2 y Cómo crear vistas deslizantes 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 de desarrollo activa y ViewPager no. Sin embargo, ViewPager2 también ofrece otras 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 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 establecer 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 RTL se habilita automáticamente cuando corresponde según la configuración regional, pero también puedes habilitarla manualmente para un elemento ViewPager2 estableciendo 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 establecer 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, que llama a notifyDatasetChanged() para actualizar la IU cuando cambia la colección subyacente.

Eso 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 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 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, debías extender la clase de adaptador que proporcionaba páginas nuevas al objeto. Según el caso de uso, 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 de constructor. Cuando extiendes FragmentStateAdapter para una clase de adaptador ViewPager2, tienes las siguientes opciones de parámetros del 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 para ViewPager2 de los que lo hicieron para ViewPager:

  • En lugar de getCount(), anula getItemCount(). Aparte del nombre, este método no se modifica.
  • En lugar de getItem(), anula createFragment() en las clases de adaptador basadas en fragmentos. Asegúrate de que el nuevo método createFragment() siempre proporcione una nueva instancia de fragmento 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 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 para 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 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 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 adjunta el objeto TabLayout al objeto ViewPager. Si bien TabLayout usa su propio método setupWithViewPager() para integrarse con ViewPager, requiere una instancia de TabLayoutMediator para 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 del 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 en los casos en los que 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 esperas desplazar el elemento anidado. En el ejemplo de desplazamiento anidado de ViewPager2, se muestra 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