ViewPager es uno de los componentes más famosos y ampliamente utilizados de la Biblioteca de soporte de Android. Todos los carruseles, incorporaciones y controles deslizantes más simples están hechos en él. En febrero de 2019, el equipo de desarrollo de AndroidX lanzó ViewPager2. Veamos cuáles eran estos requisitos previos y qué ventajas tiene la versión actualizada del componente.

ViewPager 2
En el momento de escribir la publicación (julio de 2019),
está disponible una versión beta de
ViewPager2 , lo que significa que los problemas mencionados a continuación se pueden solucionar y mejorar y ampliar la funcionalidad. Los desarrolladores prometen en el futuro agregar soporte para TabLayout (aunque solo puede funcionar con la primera versión), optimizar el rendimiento del adaptador, hacer muchas correcciones menores y finalizar la documentación.
Integración
El componente no se suministra con paquetes estándar, pero se conecta por separado. Para hacer esto, agregue la siguiente línea al bloque de dependencias en el script gradle de su módulo:
implementation "androidx.viewpager2:viewpager2:1.0.0-beta02"
Implementación
Comencemos con las buenas noticias: la transición de la primera a la segunda versión es lo más simple posible y se reduce a un cambio en las importaciones. La buena sintaxis anterior no se tocó: el método
getCurrentItem () devuelve la página actual,
ViewPager2.onPageChangeCallback le permite suscribirse al
estado del localizador, el adaptador todavía está instalado a través de
setAdapter ().
Vale la pena profundizar más, ya que queda claro que el primer y el segundo localizador no tienen nada en común excepto las interfaces. La familiaridad con la implementación del método setAdapter () no deja lugar a dudas:
public final void setAdapter(@Nullable Adapter adapter) { mRecyclerView.setAdapter(adapter); }
Sí, ViewPager2 es solo un contenedor sobre
RecyclerView . Por un lado, esta es una gran ventaja, por otro, agrega un dolor de cabeza. Disfrazar
RecyclerView como folleto se hizo posible con el advenimiento de
PagerSnapHelper . Esta clase cambia la física del desplazamiento. Cuando el usuario suelta su dedo,
PagerSnapHelper calcula qué elemento de la lista está más cerca de la línea central de la lista, y con una animación suave lo alinea exactamente en el centro. Por lo tanto, si el deslizamiento fue lo suficientemente nítido, la lista se desplaza al siguiente elemento, de lo contrario, con la animación vuelve a su estado original.
new PagerSnapHelper().attachToRecyclerView(mRecyclerView);

Cuando utilice PagerSnapHelper, asegúrese de que el ancho y el alto de RecyclerView, así como todos sus ViewHolders, estén configurados en MATCH_PARENT. De lo contrario, el comportamiento de SnapHelper será impredecible, pueden producirse errores en lugares completamente inesperados. Todo esto hace que la creación de un carrusel de elementos de pequeña altura lleve bastante tiempo, aunque sea posible.
Dado todo lo anterior, en el diseño el widget se verá así:
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/main_pager" android:layout_width="match_parent" android:layout_height="match_parent" />
En el mismo paquete que
ViewPager2, también podemos encontrar la clase
ScrollEventAdapter , que ayuda a mantener la continuidad de la sintaxis.
ScrollEventAdapter implementa
RecyclerView.OnScrollListener y transforma los eventos de desplazamiento
en eventos
OnPageChangeCallback .
@Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { if (mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG && newState == RecyclerView.SCROLL_STATE_DRAGGING) { ... dispatchStateChanged(SCROLL_STATE_DRAGGING); return; } ... }
Ahora
OnPageChangeCallback no está representado por una interfaz, sino por una clase abstracta, que le permite anular solo los métodos necesarios (en la mayoría de los casos, solo necesita
nPageSelected (Int) , que funciona cuando se selecciona una página específica):
main_pager.registerOnPageChangeCallback( object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) {
Caracteristicas
Cabe destacar el método
setPageTransformer () , que toma
ViewPager2.PageTransformer como parámetro. Establece una
devolución de
llamada para cada evento de selección de página y sirve para establecer su propia animación para esta página.
La devolución de llamada recibe la
Vista de la página actual y su número como entrada. El análogo más cercano a este método es el
ItemAnimator de
RecyclerView .
En las nuevas versiones de la biblioteca, se agregaron dos implementaciones del transformador:
CompositePageTransformer y
MarginPageTransformer . El primero es responsable de combinar transformadores para aplicar varias transformaciones a un localizador a la vez, y el segundo para sangrar entre páginas:

Además, el nuevo widget admite cambios de orientación: simplemente llamando al método
setOrientation () , puede convertir su localizador en una lista vertical con deslizamientos de arriba a abajo:
main_pager.setOrientation(ViewPager2.ORIENTATION_VERTICAL)
Esto sucede nuevamente gracias a la transición a
RecyclerView : debajo del capó,
se llama un cambio en la orientación del
LayoutManager , que es responsable de mostrar los elementos de la lista. Cabe señalar que delegar una gran cantidad de tareas a otras clases ha beneficiado al nuevo componente: su listado se ha vuelto mucho más compacto y legible.
Este no es el final de la diversión. En una actualización,
ViewPager2 recibió soporte para
ItemDecoration : una clase
auxiliar para decorar la
vista infantil. Este mecanismo se puede utilizar para dibujar separadores entre elementos, bordes, resaltado de celdas.
Ya hay muchas implementaciones de decoradores listas para usar, porque durante muchos años se han utilizado con éxito al trabajar con el
RecyclerView habitual. Todos los desarrollos ahora son aplicables a los buscapersonas. Fuera de la caja, está disponible una implementación estándar de separadores de buscapersonas:
main_pager.addItemDecoration( DividerItemDecoration(this, RecyclerView.HORIZONTAL) )
Junto con la próxima actualización en mayo de 2019,
ViewPager2 agregó otro método importante:
setOffscreenPageLimit (Int) . Él es responsable de cuántos elementos a la derecha e izquierda de la central se inicializarán en el localizador. Aunque
RecyclerView es responsable de almacenar en caché y mostrar la
Vista de forma predeterminada, con este método puede establecer explícitamente el número deseado de elementos para cargar.
Adaptador
El sucesor ideológico del primer adaptador de buscapersonas es el
FragmentStateAdapter : las interfaces de interacción y los nombres de clase son casi iguales. Los cambios solo afectaron el nombramiento de algunos métodos. Si antes era necesario implementar la función abstracta
getItem (posición) para devolver la instancia de
Fragmento deseada para la posición dada, y este nombre podría interpretarse de dos maneras, ahora esta función ha sido renombrada para
crear Fragmento (posición) . La función
getCount () proporciona el número total de fragmentos como antes.
De los cambios estructurales importantes en la interfaz, también debe tenerse en cuenta que el adaptador ahora tiene la capacidad de controlar el ciclo de vida de sus elementos, por lo tanto, junto con el
FragmentManager en el constructor, acepta un
objeto Lifecycle , ya sea una
Actividad o un
Fragmento . Debido a esto, por seguridad, los
métodos saveState () y
restoreState () se declararon definitivos y cerrados por herencia.
La clase
FragmentViewHolder es responsable de almacenar fragmentos dentro de
RecyclerView . El método
onCreateViewHolder () de
FragmentStateAdapter llama a
FragmentViewHolder.create () .
static FragmentViewHolder create(ViewGroup parent) { FrameLayout container = new FrameLayout(parent.getContext()); container.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ); container.setId(ViewCompat.generateViewId()); container.setSaveEnabled(false); return new FragmentViewHolder(container); }
Cuando se
llama al método
onBindViewHolder () , se
asocia el identificador del elemento en la posición actual y el identificador
ViewHolder , para adjuntarle aún más el fragmento:
final long itemId = holder.getItemId(); final int viewHolderId = holder.getContainer().getId(); final Long boundItemId = itemForViewHolder(viewHolderId); ... mItemIdToViewHolder.put(itemId, viewHolderId); ensureFragment(position);
Y finalmente, cuando se
adjunta un contenedor desde
ViewHolder a la jerarquía de
Vista , se ejecuta una
FragmentTransaction , agregando un
Fragment al contenedor:
void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) { Fragment fragment = mFragments.get(holder.getItemId()); ... scheduleViewAttach(fragment, container); mFragmentManager.beginTransaction() .add(fragment, "f" + holder.getItemId()) .setMaxLifecycle(fragment, STARTED) .commitNow(); ... }
Por lo tanto,
surgen dos usos de
ViewPager2 : heredando la clase de adaptador, ya sea directamente desde
RecyclerView.Adapter o desde
FragmentStateAdapter .
Seguramente tendrá una pregunta: ¿por qué usar un segundo localizador con Fragmentos y un adaptador para ellos cuando hay una primera versión que funciona normalmente?
ViewPager está lejos de ser una "bala de plata" cuando se trabaja con grandes listas de datos dinámicos. Es ideal para crear carruseles con un conjunto estático de imágenes o pancartas, pero las noticias paginadas con la carga de publicaciones publicitarias y el filtrado dan lugar a monstruos feos y con un gran apoyo. Tarde o temprano, seguramente se encontrará con un deseo ardiente de reescribir todo en
RecyclerView . Ahora no tiene que hacer esto, porque el buscapersonas mismo se convirtió en él, tomando prestadas sus poderosas capacidades para trabajar con listas dinámicas, mientras las ajusta en la sintaxis habitual.
Lo único que
PagerAdapter puede ofrecernos es el método
notifyDataSetChanged () , que obliga al
ViewPager a volver
a dibujar todos los elementos de la lista representada. Puede notar razonablemente que nadie nos impide almacenar una lista de posiciones para elementos existentes y devolver
POSITION_UNCHANGED desde el método
getItemPosition () para ellos, eso es todo. Sin embargo, esta solución no se puede llamar hermosa, es bastante engorrosa, además, es difícil ampliar en aquellos casos en que los elementos en la lista cambian constantemente, y no solo se agregan secuencialmente al final.
FragmentStateAdapter tiene un arsenal completo de métodos
RecyclerView.Adapter , por lo que la lógica de redibujar elementos se puede configurar de manera mucho más flexible. Además, junto con el
FragmentStateAdapter, puede usar
DiffUtil , que le permite automatizar casi por completo el trabajo de notificación de cambios.

Atencion Para que los métodos de notificación ... funcionen correctamente (excepto para notifyDataSetChanged ), los métodos getItemId (Int) y c ontainsItem (Long) deben redefinirse. Esto se hace porque la implementación predeterminada solo mira el número de página, y si, por ejemplo, agrega un nuevo elemento después del actual, no se agregará, ya que getItemId permanecerá sin cambios. Un ejemplo de anulación de estos dos métodos basado en una lista de elementos de tipo Int :
override fun getItemId(position: Int): Long { return items[position].toLong() } override fun containsItem(itemId: Long): Boolean { return items.contains(itemId.toInt()) }
La razón principal de la aparición de
ViewPager2 es la renuencia a reinventar la rueda. Por un lado, el
equipo de desarrollo de
AndroidX está claramente listo para abandonar el obsoleto
ViewPager ahora y ciertamente no va a invertir en expandir su funcionalidad. Si y por que? Después de todo,
RecyclerView ya sabe todo lo que se necesita. Por otro lado, la eliminación y finalización del soporte para un componente tan ampliamente utilizado obviamente no agregará lealtad a la comunidad.
Para resumir:
ViewPager2 es definitivamente digno de atención, aunque en este momento no está exento de fallas graves.
Contras
- Humedad y una gran cantidad de errores (excusable para la versión beta);
- Cercanía. RecyclerView es un campo privado de ViewPager2 , que nos priva de muchas oportunidades: es imposible implementar deslizar para descartar o arrastrar y soltar ( ItemTouchHelper se conecta directamente a RecyclerView ), no puede redefinir ItemAnimator de ninguna manera, no acceda a LayoutManager directamente y use RecycledViewPool . Sin embargo, con el lanzamiento de nuevas versiones del componente, la cantidad de métodos de interfaz heredados de RecyclerView está creciendo (por ejemplo, ItemDecoration ), y podemos esperar agregar los métodos que faltan en el futuro.
Pros
- Soporte para todas las ventajas de RecyclerView.Adapter : combina elementos de diferentes tipos en una lista, agrega y elimina elementos directamente durante el deslizamiento, redibuja animadamente el contenido de la lista al cambiar;
- Soporte para todo el espectro de los métodos de notificación ... y cálculo automático de cambios usando DiffUtil ;
- Facilidad de transición debido a la continuidad de la sintaxis;
- Soporte para orientación vertical y horizontal "fuera de la caja";
- Soporte RTL ;
- Artículo de soporte Decorador ;
- Soporte para el desplazamiento de software a través de fakeScrollBy () ;
- Capacidad para establecer manualmente el número de elementos cargados;
- La capacidad de utilizar cualquiera de las soluciones de código abierto listas para usar para reducir el código repetitivo , lo cual es inevitable cuando se escribe RecyclerView.Adapter personalizado. Por ejemplo, EasyAdapter .
Como resumen, quiero decir que realmente vale la pena ver
más de cerca ViewPager2 . Esta es una solución prometedora, extensible y funcional. Y aunque es demasiado pronto para lanzar un
nuevo widget en producción, es seguro decir que después de un lanzamiento completo puede y debe suplantar por completo a su antepasado.
Para aquellos atrevidos y decisivos, a quienes el artículo inspiró para experimentar,
PagerSnapHelper apareció en la versión 28 de la
Biblioteca de soporte , lo que significa que puede usarlo junto con su
RecyclerView creando
ViewPager2 usted mismo .
La operación de
muestra de
ViewPager2 y
FragmentStateAdapter .
Notas de lanzamiento oficiales ViewPager2