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:
- Cuando
ViewPager
usabaPagerAdapter
para cambiar las vistas, usaRecyclerView.Adapter
conViewPager2
. - Cuando
ViewPager
usabaFragmentPagerAdapter
para paginar una cantidad pequeña y fija de fragmentos, usaFragmentStateAdapter
conViewPager2
. - Cuando
ViewPager
usabaFragmentStatePagerAdapter
para cambiar un número grande o desconocido de fragmentos, usaFragmentStateAdapter
conViewPager2
.
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 objetoFragment
donde reside el objetoViewPager2
. Por lo general, esta es la mejor opción. - Un objeto
FragmentManager
y un objetoLifecycle
.
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()
, anulagetItemCount()
. Aparte del nombre, este método no se modificó. - En lugar de
getItem()
, anulacreateFragment()
en las clases de adaptadores basadas en fragmentos. Asegúrate de que el métodocreateFragment()
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:
- Cambia la superclase a
RecyclerView.Adapter
para paginar vistas oFragmentStateAdapter
para paginar fragmentos. - Cambia los parámetros del constructor en clases de adaptador basadas en fragmentos.
- Anula
getItemCount()
en lugar degetCount()
. - Anula
createFragment()
en lugar degetItem()
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
- Ejemplos de ViewPager2 en GitHub
Videos
- Pasar la página: cómo migrar a ViewPager2 (Android Dev Summit 2019)