Join FlipAndroid.COM Telegram Group: https://t.me/joinchat/F_aqThGkhwcLzmI49vKAiw


Endless RecyclerView con ProgressBar para paginación

Estoy usando un RecyclerView y obteniendo objetos de una API en lotes de diez. Para la paginación, utilizo EndlessRecyclerOnScrollListener .

Todo funciona correctamente. Ahora todo lo que queda es agregar un spinner de progreso en la parte inferior de la lista mientras que el siguiente lote de objetos es recuperado por la API. Esta es una captura de pantalla de la aplicación Google Play Store, que muestra un ProgressBar en lo que seguramente es un RecyclerView :

Introduzca aquí la descripción de la imagen

El problema es que ni el RecyclerView ni el EndlessRecyclerOnScrollListener tienen soporte incorporado para mostrar un ProgressBar en la parte inferior mientras se EndlessRecyclerOnScrollListener el siguiente lote de objetos.

Ya he visto las siguientes respuestas:

1. Poner un ProgressBar indeterminado como pie de página en una cuadrícula de RecyclerView .

2. Agregar elementos a Endless Scroll RecyclerView con ProgressBar en la parte inferior .

No estoy satisfecho con esas respuestas (ambas por la misma persona). Esto implica calzar un objeto null en el conjunto de datos a mitad de camino mientras el usuario está desplazándose y luego sacarlo después de que se entregue el siguiente lote. Parece un hack que evita el problema principal que puede o no puede funcionar correctamente. Y causa un poco de jarring y de la distorsión en la lista

Usar SwipeRefreshLayout no es una solución aquí. SwipeRefreshLayout implica tirar de la parte superior para obtener los elementos más nuevos y, de todos modos, no muestra una vista de progreso.

¿Puede alguien proporcionar una buena solución para esto? Estoy interesado en saber cómo Google ha implementado esto para sus propias aplicaciones (la aplicación de Gmail también lo tiene). ¿Hay artículos en los que se muestre esto en detalle? Todas las respuestas y comentarios serán apreciados. Gracias.

Algunas otras referencias :

1. Paginación con RecyclerView . (Excelente resumen …)

2. RecyclerView encabezado y pie de página . (Mas de lo mismo …)

3. RecyclerView sin fin con ProgressBar en la parte inferior .

  • Implementar una vista ampliable Android Recycler con encabezados pegajosos, función de búsqueda, selector lateral alfabético y Swipable Items / tarjetas?
  • Enlace de Datos de Android: Lista de Observables al Adaptador de RecyclerView
  • ¿Cómo obtener información sobre el toque de RecyclerView?
  • ¿Cómo puedo hacer que WRAP_CONTENT funcione en un RecyclerView
  • Los elementos no tienen el mismo ancho cuando se utiliza RecyclerView GridLayoutManager para establecer el espaciado de columnas por ItemDecoration
  • Cómo configurar la aplicación RecyclerView: layoutManager = "" de XML?
  • Android Cómo reciclar el mapa de bits correctamente cuando se utiliza RecyclerView?
  • Android: Control Desplazamiento suave sobre la vista del reciclador
  • 4 Solutions collect form web for “Endless RecyclerView con ProgressBar para paginación”

    He implementado esto en mi antiguo proyecto, lo hice de la siguiente manera …

    He creado una interface como lo hicieron los chicos de tus ejemplos

     public interface LoadMoreItems { void LoadItems(); } 

    Entonces agrego agregado un addOnScrollListener() en mi Adapter

      recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); totalItemCount = linearLayoutManager.getItemCount(); lastVisibleItem = linearLayoutManager .findLastVisibleItemPosition(); if (!loading && totalItemCount <= (lastVisibleItem + visibleThreshold)) { //End of the items if (onLoadMoreListener != null) { onLoadMoreListener.LoadItems(); } loading = true; } } }); 

    El onCreateViewHolder() es donde pongo el ProgressBar o no.

     @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder vh; if (viewType == VIEW_ITEM) { View v = LayoutInflater.from(parent.getContext()).inflate( R.layout.list_row, parent, false); vh = new StudentViewHolder(v); } else { View v = LayoutInflater.from(parent.getContext()).inflate( R.layout.progressbar_item, parent, false); vh = new ProgressViewHolder(v); } return vh; } 

    En mi MainActivity que es donde pongo el LoadItems() para agregar los otros elementos es:

     mAdapter.setOnLoadMoreListener(new LoadMoreItems() { @Override public void LoadItems() { DataItemsList.add(null); mAdapter.notifyItemInserted(DataItemsList.size() - 1); handler.postDelayed(new Runnable() { @Override public void run() { // remove progress item DataItemsList.remove(DataItemsList.size() - 1); mAdapter.notifyItemRemoved(DataItemsList.size()); //add items one by one //When you've added the items call the setLoaded() mAdapter.setLoaded(); //if you put all of the items at once call // mAdapter.notifyDataSetChanged(); } }, 2000); //time 2 seconds } }); 

    Para más información sólo he seguido este Github repository ( Nota: esto es utilizando AsyncTask tal vez es útil como mi respuesta, ya que lo hice manualmente no con los datos de la API pero debería funcionar también ) también esta publicación me fue útil sin fin-recyclerview Con barra de progreso

    También no sé si lo nombró, pero también encontré este post infinite_scrolling_recyclerview , tal vez también podría ayudar a usted.

    Si no es lo que estás buscando, hazme saber y dime qué tiene de malo este código e intentaré modificarlo como tu.

    Espero eso ayude.

    EDITAR

    Puesto que usted no desea quitar un artículo … Encontré que conjeturo a un individuo que quita el footer solamente en este poste: diseño-androide-sin fin-recyclerview .

    Esto es para ListView pero sé que puedes adaptarlo a RecyclerView que no está borrando ningún elemento que está poniendo Visible/Invisible ProgressBar echa un vistazo: detecting-end-of-listview

    También echar un vistazo a esta pregunta android-implementing-progressbar-and-loading-for-endless-list-like-android

    Hay otra manera de hacer esto.

    • En primer lugar getItemCount de su adaptador devuelve listItems.size() + 1
    • VIEW_TYPE_LOADING en getItemViewType() para la position >= listItems.size() . De esta manera, el cargador sólo se mostrará al final de la lista de visualización del reciclador. El único problema con esta solución es incluso después de llegar a la última página, se mostrará el cargador, por lo que para arreglar que almacena la x-pagination-total-count en el adaptador, y
    • Cambia la condición para devolver el tipo de vista a

      (position >= listItem.size())&&(listItem.size <= xPaginationTotalCount) .

    Acabo de llegar a esta idea ahora ¿qué te parece?

    Me gusta la idea de añadir un titular de vista de progreso a un adaptador, pero tiende a conducir a una manipulación lógica fea para obtener lo que quieres. El enfoque del titular de la vista le obliga a protegerse contra el elemento de pie de página adicional moviéndose con los valores de retorno de getItemCount() , getItemViewType() , getItemId(position) y cualquier tipo de getItem(position) que desee incluir.

    Un enfoque alternativo es administrar la visibilidad de ProgressBar en el nivel de Fragment o Activity al mostrar u ocultar el ProgressBar debajo del RecyclerView cuando se inicia y termina la carga, respectivamente. Esto se puede lograr mediante la inclusión de ProgressBar directamente en el diseño de vista o agregándolo a una clase personalizada de RecyclerView ViewGroup . Esta solución generalmente llevará a menos mantenimiento y menos errores.

    ACTUALIZACIÓN: Mi sugerencia plantea un problema al desplazar la vista de respaldo mientras el contenido se está cargando. El ProgressBar se pegará a la parte inferior del diseño de la vista. Este no es probablemente el comportamiento que desea. Por esta razón, la adición de un titular de vista de progreso a su adaptador es probablemente la mejor solución funcional. Apenas no olvide guardar sus métodos del accesorio del artículo. 🙂

    Diferente enfoque sería iniciar la llamada de la API dentro onBindViewHolder e inicialmente colocar en la vista de elementos algún indicador de progreso. Una vez finalizada la llamada, actualiza la vista (ocultar el progreso y mostrar los datos recibidos). Por ejemplo, con Picasso para la carga de imágenes, el método onBindViewHolder se vería así

     @Override public void onBindViewHolder(final MovieViewHolder holder, final int position) { final Movie movie = items.get(position); holder.imageProgress.setVisibility(View.VISIBLE); Picasso.with(context) .load(NetworkingUtils.getMovieImageUrl(movie.getPosterPath())) .into(holder.movieThumbImage, new Callback() { @Override public void onSuccess() { holder.imageProgress.setVisibility(View.GONE); } @Override public void onError() { } }); } 

    Como lo veo, hay dos casos que pueden aparecer:

    1. Donde descargas todos los elementos en versión ligera con una llamada (por ejemplo, el adaptador sabe inmediatamente que tendrá que tratar con 40 imágenes, pero las descargas a demanda -> caso que mostré anteriormente con Picasso)
    2. Donde usted está trabajando con la carga real perezoso y le está pidiendo al servidor para darle un trozo adicional de datos. En este caso, el primer requisito es tener una respuesta adecuada del servidor con la información necesaria. Ejemplo anterior {"offset": 0, "total": 100, "items": [{items}]}

    Hay respuesta significa que recibió la primera parte del total de 100 datos. Mi enfoque sería algo como esto:

    Ver Después de obtener el primer trozo de datos (por ejemplo, 10) agregarlos en el adaptador.

    RecyclerView.Adapter.getItemCount Siempre y cuando la cantidad actual de elementos disponibles sea inferior a la cantidad total (por ejemplo, 10 disponibles, total 100), en el método getItemCount volverá items.size () + 1

    RecyclerView.Adapter.getItemViewType si la cantidad total de datos es mayor que la cantidad de elementos disponibles en el adaptador y la posición = items.size () (es decir, ha agregado un elemento fictively en el método getItemCount), como tipo de vista devuelve algún indicador de progreso . De lo contrario, devolverá el tipo de diseño normal

    RecyclerView.Adapter.onCreateViewHolder Cuando se le pide que utilice el tipo de vista del indicador de progreso, todo lo que necesita hacer es pedirle a su presentador que obtenga un trozo adicional de elementos y actualice el adaptador

    Así que, básicamente, este es el enfoque en el que no tiene que agregar / quitar elementos de la lista y donde tiene control sobre la situación cuando la carga perezosa se disparará.

    Aquí está el ejemplo de código:

     public class ForecastListAdapter extends RecyclerView.Adapter<ForecastListAdapter.ForecastVH> { private final Context context; private List<Forecast> items; private ILazyLoading lazyLoadingListener; public static final int VIEW_TYPE_FIRST = 0; public static final int VIEW_TYPE_REST = 1; public static final int VIEW_TYPE_PROGRESS = 2; public static final int totalItemsCount = 14; public ForecastListAdapter(List<Forecast> items, Context context, ILazyLoading lazyLoadingListener) { this.items = items; this.context = context; this.lazyLoadingListener = lazyLoadingListener; } public void addItems(List<Forecast> additionalItems){ this.items.addAll(additionalItems); notifyDataSetChanged(); } @Override public int getItemViewType(int position) { if(totalItemsCount > items.size() && position == items.size()){ return VIEW_TYPE_PROGRESS; } switch (position){ case VIEW_TYPE_FIRST: return VIEW_TYPE_FIRST; default: return VIEW_TYPE_REST; } } @Override public ForecastVH onCreateViewHolder(ViewGroup parent, int viewType) { View v; switch (viewType){ case VIEW_TYPE_PROGRESS: v = LayoutInflater.from(parent.getContext()).inflate(R.layout.forecast_list_item_progress, parent, false); if (lazyLoadingListener != null) { lazyLoadingListener.getAdditionalItems(); } break; case VIEW_TYPE_FIRST: v = LayoutInflater.from(parent.getContext()).inflate(R.layout.forecast_list_item_first, parent, false); break; default: v = LayoutInflater.from(parent.getContext()).inflate(R.layout.forecast_list_item_rest, parent, false); break; } return new ForecastVH(v); } @Override public void onBindViewHolder(ForecastVH holder, int position) { if(position < items.size()){ Forecast item = items.get(position); holder.date.setText(FormattingUtils.formatTimeStamp(item.getDt())); holder.minTemperature.setText(FormattingUtils.getRoundedTemperature(item.getTemp().getMin())); holder.maxTemperature.setText(FormattingUtils.getRoundedTemperature(item.getTemp().getMax())); } } @Override public long getItemId(int position) { long i = super.getItemId(position); return i; } @Override public int getItemCount() { if (items == null) { return 0; } if(items.size() < totalItemsCount){ return items.size() + 1; }else{ return items.size(); } } public class ForecastVH extends RecyclerView.ViewHolder{ @BindView(R.id.forecast_date)TextView date; @BindView(R.id.min_temperature)TextView minTemperature; @BindView(R.id.max_temperature) TextView maxTemperature; public ForecastVH(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } } public interface ILazyLoading{ public void getAdditionalItems(); }} 

    Tal vez esto le inspire a hacer algo que se adapte a sus necesidades

    FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.