recyclerview ejemplo cardview android android-listview android-recyclerview android-5.0-lollipop android-progressbar

recyclerview - cardview android ejemplo



RecyclerView sin fin con ProgressBar para paginaciĆ³n (8)

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

Todo funciona correctamente. Ahora todo lo que queda es agregar una flecha de progreso al final de la lista mientras la API recupera el siguiente lote de objetos. Aquí hay una captura de pantalla de la aplicación Google Play Store, que muestra una ProgressBar de ProgressBar en lo que seguramente es un RecyclerView :

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

Ya he visto las siguientes respuestas:

1. Coloque una ProgressBar indeterminada como pie de página en una cuadrícula 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 un horquillado de un objeto null en el conjunto de datos a mitad de camino mientras el usuario se desplaza y luego lo saca después de entregar el siguiente lote. Parece un truco que evita el problema principal que puede o no funcionar correctamente. Y causa un poco de discordancia y distorsión en la lista

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

¿Alguien puede proporcionar una buena solución para esto? Estoy interesado en saber cómo Google ha implementado esto para sus propias aplicaciones (la aplicación Gmail también lo tiene). ¿Hay algún artículo donde esto se muestra en detalle? Todas las respuestas y comentarios serán apreciados. Gracias.

Algunas otras referencias :

1. Paginación con RecyclerView . (Resumen excelente ...)

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

3. Endless RecyclerView con ProgressBar en la parte inferior .


Aquí está mi solución sin agregar un artículo falso (en Kotlin pero simple):

en su adaptador agregue:

private var isLoading = false private val VIEWTYPE_FORECAST = 1 private val VIEWTYPE_PROGRESS = 2 override fun getItemCount(): Int { if (isLoading) return items.size + 1 else return items.size } override fun getItemViewType(position: Int): Int { if (position == items.size - 1 && isLoading) return VIEWTYPE_PROGRESS else return VIEWTYPE_FORECAST } override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder { if (viewType == VIEWTYPE_FORECAST) return ForecastHolder(LayoutInflater.from(context).inflate(R.layout.item_forecast, parent, false)) else return ProgressHolder(LayoutInflater.from(context).inflate(R.layout.item_progress, parent, false)) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) { if (holder is ForecastHolder) { //init your item } } public fun showProgress() { isLoading = true } public fun hideProgress() { isLoading = false }

ahora puede llamar fácilmente a showProgress() antes de la llamada API. y hideProgress() después de que se realizó la llamada API.


Esta solución está inspirada en la solución de Akshar Patels en esta página. Lo modifiqué un poco.

  1. Al cargar los primeros elementos, parece bueno tener la barra de progreso centrada.

  2. No me gustó el relleno vacío restante en la parte inferior cuando no había más elementos para cargar. Eso se ha eliminado con esta solución.

Primero el XML:

<android.support.v7.widget.RecyclerView android:id="@+id/video_list" android:paddingBottom="60dp" android:clipToPadding="false" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> </android.support.v7.widget.RecyclerView> <ProgressBar android:id="@+id/progressBar2" style="?android:attr/progressBarStyle" android:layout_marginTop="10dp" android:layout_width="wrap_content" android:layout_centerHorizontal="true" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/>

Luego agregué lo siguiente mediante programación.

Cuando se cargan los primeros resultados, agréguelo a su onScrollListener. Mueve la barra de progreso desde el centro hacia abajo:

ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) loadingVideos.getLayoutParams(); layoutParams.topToTop = ConstraintLayout.LayoutParams.UNSET; loadingVideos.setLayoutParams(layoutParams);

Cuando no existan más elementos, elimine el relleno en la parte inferior de esta manera:

recyclerView.setPadding(0,0,0,0);

Oculta y muestra tu ProgressBar como siempre.


Hay otra forma de hacer esto.

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

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

Se me ocurrió esta idea ahora, ¿qué te parece?


Implementé esto en mi antiguo proyecto, lo hice de la siguiente manera ...

He creado una interface como hicieron los chicos de tus ejemplos

public interface LoadMoreItems { void LoadItems(); }

Luego agregué 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 puse los 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 obtener más información, acabo de seguir este Github repository ( Nota: esto está usando AsyncTask tal vez sea útil como respuesta, ya que lo hice manualmente no con datos de la API pero también debería funcionar ). También esta publicación fue útil para mí. endless-recyclerview-with-progress-bar

Además, no sé si lo nombró, pero también encontré esta publicación infinite_scrolling_recyclerview , tal vez también podría ayudarlo.

Si no es lo que está buscando, avíseme y dígame qué le pasa a este código e intentaré modificarlo a su gusto.

Espero eso ayude.

EDITAR

Como no quieres eliminar un elemento ... Encontré un tipo que elimina el footer solo en esta publicación: diseño-android-endless-recyclerview .

Esto es para ListView pero sé que puede adaptarlo a RecyclerView , no está eliminando ningún elemento que solo está poniendo Visible/Invisible ProgressBar eche un vistazo: detecting-end-of-listview

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


Me gusta la idea de agregar un titular de vista de progreso a un adaptador, pero tiende a llevar a una manipulación lógica fea para obtener lo que desea. El enfoque del titular de la vista lo obliga a protegerse contra el elemento de pie de página adicional al moverse 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 Fragment o Activity mostrando u ocultando ProgressBar debajo de RecyclerView cuando la carga comienza y finaliza, respectivamente. Esto se puede lograr al incluir ProgressBar directamente en el diseño de la vista o al agregarlo a una clase personalizada RecyclerView ViewGroup . Esta solución generalmente generará menos mantenimiento y menos errores.

ACTUALIZACIÓN: Mi sugerencia plantea un problema cuando vuelve a desplazar la vista mientras se carga el contenido. ProgressBar mantendrá en la parte inferior del diseño de la vista. Probablemente este no sea el comportamiento que desea. Por esta razón, agregar un soporte de vista de progreso a su adaptador es probablemente la mejor solución funcional. Simplemente no te olvides de proteger los métodos de acceso de tu artículo. :)


Puede usar la etiqueta layout_above en recycleView así:

<RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:orientation="vertical"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv" android:layout_below="@+id/tv2" android:layout_width="match_parent" android:focusable="false" android:layout_height="wrap_content" android:layout_marginTop="24dp" android:layout_above="@+id/pb_pagination"/> <ProgressBar android:id="@+id/pb_pagination" style="@style/Widget.AppCompat.ProgressBar" android:layout_width="30dp" android:layout_height="30dp" android:indeterminate="true" android:visibility="gone" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" /> </RelativeLayout>


Un enfoque diferente sería iniciar la llamada API dentro de onBindViewHolder e inicialmente colocar en elementos ver algún indicador de progreso. Una vez finalizada la llamada, actualiza la vista (oculta el progreso y muestra los datos recibidos). Por ejemplo, con Picasso para cargar 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() { } }); }

Tal como lo veo, hay dos casos que pueden aparecer:

  1. donde descarga todos los elementos en versión ligera con una sola llamada (por ejemplo, el adaptador sabe de inmediato que tendrá que lidiar con 40 imágenes, pero las descarga a pedido -> caso que mostré previamente con Picasso)
  2. donde está trabajando con una carga diferida real y está pidiendo al servidor que le brinde una porción adicional de datos. En este caso, el primer requisito previo es tener una respuesta adecuada del servidor con la información necesaria. Ejemplo anterior {"offset": 0, "total": 100, "items": [{items}]}

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

Ver Después de obtener la primera porción de datos (por ejemplo, 10) agréguelos al adaptador.

RecyclerView.Adapter.getItemCount Siempre que la cantidad actual de elementos disponibles sea inferior a la cantidad total (por ejemplo, disponible 10; total 100), en el método getItemCount devolverá 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 elementos de forma ficticia 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 use el tipo de vista de indicador de progreso, todo lo que necesita hacer es pedirle a su presentador que obtenga una porción adicional de elementos y actualice el adaptador

Básicamente, este es un enfoque en el que no tiene que agregar / eliminar elementos de la lista y donde tiene control sobre la situación cuando se activará la carga diferida.

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

Quizás esto te inspire a hacer algo que se adapte a tus necesidades


AQUÍ ESTÁ UN ENFOQUE MÁS SENCILLO Y LIMPIO.

Implemente Desplazamiento sin fin desde esta guía de Codepath y luego siga los siguientes pasos.

1. Agregue la barra de progreso debajo de RecyclerView.

<android.support.v7.widget.RecyclerView android:id="@+id/rv_movie_grid" android:layout_width="0dp" android:layout_height="0dp" android:paddingBottom="50dp" android:clipToPadding="false" android:background="@android:color/black" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> </android.support.v7.widget.RecyclerView> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="invisible" android:background="@android:color/transparent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" />

Aquí android: paddingBottom = "50dp" y android: clipToPadding = "false" son muy importantes.

2. Obtenga una referencia a la barra de progreso.

progressBar = findViewById(R.id.progressBar);

3. Defina métodos para mostrar y ocultar la barra de progreso.

void showProgressView() { progressBar.setVisibility(View.VISIBLE); } void hideProgressView() { progressBar.setVisibility(View.INVISIBLE); }