Diseño Android : Endless RecyclerView

Última actualización: 23/11/2016

android

En numerosos listados en los que se puedan mostrar una gran cantidad de elementos resulta necesario algún mecanismo de paginación que permita ir obteniendo, generalmente desde un servicio web REST, los elementos a mostrar a medida que el usuario solicita más datos al hacer scroll del listado:

  • Hacia arriba. Si existen elementos más recientes que los actualmente mostrados deberían añadirse en la parte superior del listado. Por tanto, en este tipo de listados al intentar avanzar más allá del primer elemento, se obtienen los más recientes y se añaden. Este patrón es fácil de utilizar gracias a SwipeRefreshLayout.
  • Hacia abajo. En este caso, si hay elementos más antiguos que los actualmente mostrados estos se irán añadiendo en la parte inferior de la lista a medida que el usuario la vaya deslizando hacia su final. Se muestra una barra de progreso indeterminado al final de la lista mientras se realiza la obtención de los siguientes elementos; en raras ocasiones se muestra un botón para que el usuario solicite explícitamente más datos.

youtube endless

El segundo comportamiento fue implementado en el tutorial Android ListView: Estrategias de paginación y en esta ocasión haremos lo mismo pero utilizando un RecyclerView, en concreto el desarrollado en el tutorial Android Recycler View: Listas por lo que recomiendo echarle un vistazo para mejorar la comprensión del código del presente tutorial.

Podemos dividir la implementación cuatro pasos:

  1. Detectar si el usuario ha hecho scroll hasta la última posición del listado utilizando el evento RecyclerView.OnScrollListener. La posición del último elemento mostrado en pantalla se obtendrá directamente del LinearLayoutManager. Asimismo, usaremos un flag para saber si quedan más elementos por obtenerse.

     recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
          @Override
          public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (hasMore && !(hasFooter())) {
              LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
              //position starts at 0
              if (layoutManager.findLastCompletelyVisibleItemPosition()
                  == layoutManager.getItemCount() - 2) {
    
                //displays the footer after the onscroll listener
                colors.add(new Footer());
                Handler handler = new Handler();
    
                final Runnable r = new Runnable() {
                  public void run() {
                    MainActivity.this.recyclerView.getAdapter().notifyItemInserted(colors.size() - 1);
                  }
                };
                handler.post(r);
                asyncTask = new BackgroundTask();
                asyncTask.execute((Object[]) null);
              }
            }
          }
        });
    

    Para conocer si ya se está realizando una carga de nuevos elementos se comprueba si se está mostrando el ProgressDialog (ver punto siguiente). Esto implica que el footer no se puede eliminar del RecyclerView hasta que no haya finalizado todo el proceso de carga en segundo plano.

    private boolean hasFooter() {
        return colors.get(colors.size() - 1) instanceof Footer;
      }
    
  2. Mientras se realiza la carga de lo nuevos datos en segundo plano gracias a la AsyncTask se mostrará un ProgressDialog.
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    
        <ProgressBar
            android:id="@+id/footer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal" />
    
    </LinearLayout>
    
    

    En un ListView esta tarea es inmediata puesto que podemos mostrar y ocultar un footer, pero en RecyclerView es necesario implementar esta funcionalidad. La estrategia que suelo emplear para mostrar filas de distinto tipo en un RecyclerView consiste en utilizar como dataset para el Adpater una lista con objetos de los distintos tipos que se utilicen de tal modo que cada tipo (clase) tendrá su propio ViewHolder y View. En función del tipo de objeto que RecyclerView intente mostrar en cada momento al llamar a onCreateViewHolder se utilizará el ViewHolder y layout correspondiente al elemento de la posición deseada. El tipo de objeto lo informaremos mediante un entero implementando el método getItemViewType.

    public class MaterialPaletteAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
      private List<Item> data;
      private RecyclerViewOnItemClickListener recyclerViewOnItemClickListener;
    
      private static final int TYPE_COLOR = 0;
      private static final int TYPE_FOOTER = 1;
    
      public MaterialPaletteAdapter(@NonNull List<Item> data, RecyclerViewOnItemClickListener
          recyclerViewOnItemClickListener) {
        this.data = data;
        this.recyclerViewOnItemClickListener = recyclerViewOnItemClickListener;
      }
    
      @Override
      public int getItemViewType(int position) {
        if (data.get(position) instanceof Color) {
          return TYPE_COLOR;
        } else if (data.get(position) instanceof Footer) {
          return TYPE_FOOTER;
        } else {
          throw new RuntimeException("ItemViewType unknown");
        }
      }
    
      @Override
      public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_COLOR) {
          View row = LayoutInflater.from(parent.getContext()).inflate(R.layout.row, parent, false);
          return new PaletteViewHolder(row, recyclerViewOnItemClickListener);
        } else {
          View row = LayoutInflater.from(parent.getContext()).inflate(R.layout.progress_footer,
              parent, false);
          return new FooterViewHolder(row);
        }
      }
    
      @Override
      public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof PaletteViewHolder) {
          Color color = (Color) data.get(position);
    
          PaletteViewHolder paletteViewHolder = (PaletteViewHolder) holder;
          paletteViewHolder.getTitleTextView().setText(color.getName());
          paletteViewHolder.getSubtitleTextView().setText(color.getHex());
    
          GradientDrawable gradientDrawable = (GradientDrawable) paletteViewHolder.getCircleView()
              .getBackground();
          int colorId = android.graphics.Color.parseColor(color.getHex());
          gradientDrawable.setColor(colorId);
        }
        //FOOTER: nothing to do
    
      }
    
      @Override
      public int getItemCount() {
        return data.size();
      }
    
      private static class FooterViewHolder extends RecyclerView.ViewHolder {
        public ProgressBar getProgressBar() {
          return progressBar;
        }
    
        private ProgressBar progressBar;
    
        public FooterViewHolder(View itemView) {
          super(itemView);
          progressBar = (ProgressBar) itemView.findViewById(R.id.footer);
        }
    }
    ...
    

    Ver MaterialPaletteAdapter.java.

  3. La AsyncTask realiza la carga de datos en segundo plano y actualiza el listado. Con Thread.sleep() se simula la demora en la obtención de los datos.
    private class BackgroundTask extends AsyncTask<Void, Void, Void> {
    
        @Override
        protected Void doInBackground(Void... params) {
          try {
            Thread.sleep(3000);
          } catch (InterruptedException e) {
            Log.e(this.getClass().toString(), e.getMessage());
          }
          return null;
        }
    
        @Override
        protected void onPostExecute(Void aVoid) {
          int size = colors.size();
          colors.remove(size - 1);//removes footer
          colors.addAll(buildColors());
          recyclerView.getAdapter().notifyItemRangeChanged(size - 1, colors.size() - size);
        }
    
      }
        
    

    Ver MainActivity.java.

El siguiente video muestra el resultado final del proyecto de ejemplo.



Código de ejemplo

El proyecto de ejemplo para Android Studio se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.

3 Responses to Diseño Android : Endless RecyclerView

  1. miguel dice:

    gracias me sirvio de mucho

  2. Mario dice:

    Gracias por compartir tu conocimiento con todos, te queria preguntar como puedo colocar un spinner dentro de un recyclcerview, cuentas con algun tutorial o link al respecto, te agradeceria demasiado.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: