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.
-
Al cargar los primeros elementos, parece bueno tener la barra de progreso centrada.
-
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
engetItemViewType()
paraposition >= 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 elx-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:
- 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)
- 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);
}