ViewPager 2: nueva funcionalidad en el contenedor antiguo

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

imagen
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) { //do your stuff } } ) 

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:

imagen

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

Source: https://habr.com/ru/post/461873/


All Articles