Cómo crear una lista con RecyclerView

Cómo crear una lista con RecyclerView   Parte de Android Jetpack

Si tu app debe mostrar una lista de desplazamiento de elementos sobre la base de grandes conjuntos de datos (o datos que cambian con frecuencia), debes usar el objeto RecyclerView, según se describe en esta página.

Sugerencia: Haz clic en File > New > Fragment > Fragment (List) para comenzar con parte de código de plantillas en Android Studio. Luego, simplemente agrega el fragmento al diseño de tu actividad.

Figura 1: Una lista que usa RecyclerView

Figura 2: Una lista que también usa CardView

Si te gustaría crear una lista con tarjetas, como se muestra en la figura 2, usa también el widget de CardView, como se describe en Cómo crear un diseño basado en tarjetas.

Si te gustaría ver un fragmento de código de ejemplo de RecyclerView, consulta la app de ejemplo de RecyclerView Java | Kotlin.

Descripción general de RecyclerView

El widget RecyclerView es una versión más avanzada y flexible de ListView.

En el modelo de RecyclerView, varios componentes diferentes trabajan juntos para mostrar tus datos. El contenedor general de tu interfaz de usuario es un objeto RecyclerView que agregas a tu diseño. El objeto RecyclerView se completa por sí solo con vistas que brinda el administrador de diseño que proporciones. Puedes usar uno de nuestros administradores de diseño estándar (como LinearLayoutManager o GridLayoutManager), o bien implementar uno propio.

Las vistas incluidas en la lista están representadas por objetos contenedores de vistas. Esos objetos son instancias de una clase que defines extendiendo el objeto RecyclerView.ViewHolder. Cada objeto contenedor de vistas es responsable de mostrar un elemento individual con una vista. Por ejemplo, si tu lista muestra la colección de música, cada objeto contenedor de vistas representa un álbum individual. El objeto RecyclerView crea solamente la cantidad de objetos contenedores de vistas que sean necesarios para mostrar la parte en pantalla del contenido dinámico, más algunos adicionales. A medida que el usuario se desplaza por la lista, el objeto RecyclerView toma las vistas fuera de pantalla y vuelve a vincularlas con los datos que se desplazan en la pantalla.

Un adaptador, que creas extendiendo RecyclerView.Adapter, administra los objetos contenedores de vistas. El adaptador crea contenedores de vistas, según sea necesario, y los vincula con sus datos. Para hacerlo, asigna el contenedor de vistas a una posición y llama al método onBindViewHolder() del adaptador. Este método usa la posición del contenedor de vistas para determinar cuál debería ser el contenido, en función de su posición en la lista.

Este modelo de RecyclerView realiza gran parte del trabajo de optimización por ti:

  • Cuando se completa por primera vez la lista, crea y vincula algunos contenedores de vistas a cualquier lado de la lista. Por ejemplo, si la vista muestra las posiciones de la lista 0 a 9, el objeto RecyclerView crea y vincula estos contenedores de vistas, y es posible que también cree y vincule el contenedor de vistas para la posición 10. De esta manera, si el usuario se desplaza por la vista, el siguiente elemento estará listo para mostrarse.
  • A medida que el usuario se desplaza por la vista, el objeto RecyclerView crea nuevos contenedores de vistas según sea necesario. También guarda los contenedores de vistas que se desplazaron fuera de la pantalla para que puedan volver a usarse. Si el usuario cambia la dirección de desplazamiento, se pueden recuperar los contenedores de vistas que se desplazaron fuera de la pantalla. Por otro lado, si el usuario continúa el desplazamiento en la misma dirección, los contenedores de vistas que salieron de la pantalla por más tiempo pueden volver a vincularse con datos nuevos. No es necesario crear el contenedor de vistas o aumentar su vista; en cambio, la app simplemente actualiza el contenido de la vista para que coincida con el elemento nuevo con el que está vinculada.
  • Cuando cambien los elementos que se muestren, podrás notificar al adaptador llamando a un método RecyclerView.Adapter.notify…() adecuado. Luego, el código integrado del adaptador vuelve a vincular los elementos afectados.

Cómo agregar la biblioteca de compatibilidad

Para acceder al widget de RecyclerView, es necesario agregar las Bibliotecas de compatibilidad v7 a tu proyecto de la siguiente manera:

  1. Abre el archivo build.gradle del módulo de tu app.
  2. Agrega la biblioteca de compatibilidad a la sección dependencies.
        dependencies {
            implementation 'com.android.support:recyclerview-v7:28.0.0'
        }
        

Cómo agregar RecyclerView a tu diseño

Ahora puedes agregar RecyclerView a tu archivo de diseño. Por ejemplo, el siguiente diseño usa RecyclerView como la única vista para todo el diseño:

    <?xml version="1.0" encoding="utf-8"?>
    <!-- A RecyclerView with some commonly used attributes -->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    

Después de agregar un widget de RecyclerView a tu diseño, obtén un controlador para el objeto, conéctalo a un administrador de diseño y adjunta un adaptador para que se muestren los datos:

Kotlin

    class MyActivity : Activity() {
        private lateinit var recyclerView: RecyclerView
        private lateinit var viewAdapter: RecyclerView.Adapter<*>
        private lateinit var viewManager: RecyclerView.LayoutManager

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.my_activity)

            viewManager = LinearLayoutManager(this)
            viewAdapter = MyAdapter(myDataset)

            recyclerView = findViewById<RecyclerView>(R.id.my_recycler_view).apply {
                // use this setting to improve performance if you know that changes
                // in content do not change the layout size of the RecyclerView
                setHasFixedSize(true)

                // use a linear layout manager
                layoutManager = viewManager

                // specify an viewAdapter (see also next example)
                adapter = viewAdapter

            }
        }
        // ...
    }
    

Java

    public class MyActivity extends Activity {
        private RecyclerView recyclerView;
        private RecyclerView.Adapter mAdapter;
        private RecyclerView.LayoutManager layoutManager;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.my_activity);
            recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);

            // use this setting to improve performance if you know that changes
            // in content do not change the layout size of the RecyclerView
            recyclerView.setHasFixedSize(true);

            // use a linear layout manager
            layoutManager = new LinearLayoutManager(this);
            recyclerView.setLayoutManager(layoutManager);

            // specify an adapter (see also next example)
            mAdapter = new MyAdapter(myDataset);
            recyclerView.setAdapter(mAdapter);
        }
        // ...
    }
    

Cómo agregar un adaptador de listas

Para ingresar todos tus datos a la lista, debes extender la clase RecyclerView.Adapter. Este objeto crea vistas para elementos y reemplaza el contenido de algunas de las vistas por nuevos elementos de datos cuando el elemento original ya no está visible.

El código de ejemplo que se incluye a continuación muestra una implementación simple de un conjunto de datos que comprende un arreglo de strings que se muestra con widgets de TextView:

Kotlin

    class MyAdapter(private val myDataset: Array<String>) :
            RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

        // Provide a reference to the views for each data item
        // Complex data items may need more than one view per item, and
        // you provide access to all the views for a data item in a view holder.
        // Each data item is just a string in this case that is shown in a TextView.
        class MyViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)

        // Create new views (invoked by the layout manager)
        override fun onCreateViewHolder(parent: ViewGroup,
                                        viewType: Int): MyAdapter.MyViewHolder {
            // create a new view
            val textView = LayoutInflater.from(parent.context)
                    .inflate(R.layout.my_text_view, parent, false) as TextView
            // set the view's size, margins, paddings and layout parameters
            ...
            return MyViewHolder(textView)
        }

        // Replace the contents of a view (invoked by the layout manager)
        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            // - get element from your dataset at this position
            // - replace the contents of the view with that element
            holder.textView.text = myDataset[position]
        }

        // Return the size of your dataset (invoked by the layout manager)
        override fun getItemCount() = myDataset.size
    }
    

Java

    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
        private String[] mDataset;

        // Provide a reference to the views for each data item
        // Complex data items may need more than one view per item, and
        // you provide access to all the views for a data item in a view holder
        public static class MyViewHolder extends RecyclerView.ViewHolder {
            // each data item is just a string in this case
            public TextView textView;
            public MyViewHolder(TextView v) {
                super(v);
                textView = v;
            }
        }

        // Provide a suitable constructor (depends on the kind of dataset)
        public MyAdapter(String[] myDataset) {
            mDataset = myDataset;
        }

        // Create new views (invoked by the layout manager)
        @Override
        public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
                                                       int viewType) {
            // create a new view
            TextView v = (TextView) LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.my_text_view, parent, false);
            ...
            MyViewHolder vh = new MyViewHolder(v);
            return vh;
        }

        // Replace the contents of a view (invoked by the layout manager)
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            // - get element from your dataset at this position
            // - replace the contents of the view with that element
            holder.textView.setText(mDataset[position]);

        }

        // Return the size of your dataset (invoked by the layout manager)
        @Override
        public int getItemCount() {
            return mDataset.length;
        }
    }
    

El administrador de diseño llama al método onCreateViewHolder() del adaptador. Es necesario que este método construya un objeto RecyclerView.ViewHolder y configure la vista que usa para mostrar su contenido. El tipo del contenedor de vistas debe coincidir con el tipo declarado en la firma de la clase Adapter. Por lo general, configuraría la vista aumentando un archivo de diseño XML. Dado que el contenedor de vistas todavía no está asignado a ningún dato en particular, el método no configura realmente el contenido de la vista.

Luego, el administrador de diseño vincula el contenedor de vistas con sus datos. Para hacerlo, llama al método onBindViewHolder() del adaptador y pasa la posición del contenedor de vistas en RecyclerView. Es necesario que el método onBindViewHolder() busque los datos correspondientes y los use para completar el diseño del contenedor de vistas. Por ejemplo, si RecyclerView muestra una lista de nombres, es posible que el método encuentre el nombre adecuado en la lista y complete el widget de TextView del contenedor de vistas.

Si es necesario actualizar la lista, llama a un método de notificación en el objeto RecyclerView.Adapter, por ejemplo notifyItemChanged(). El administrador de diseño vincula nuevamente cualquier contenedor de vistas afectado, lo que permite la actualización de sus datos.

Sugerencia: Es probable que la clase ListAdapter te resulte útil para determinar los elementos de la lista que deben actualizarse cuando esta cambie.

Cómo personalizar un objeto RecyclerView

Puedes personalizar los objetos RecyclerView para satisfacer tus necesidades específicas. Las clases estándar ofrecen toda la funcionalidad necesaria por la mayoría de los desarrolladores; en muchos casos, la única personalización que debes hacer es diseñar la vista para cada contenedor de vistas y escribir el código para actualizar esas vistas con los datos correspondientes. No obstante, si tu app tiene requisitos específicos, puedes modificar el comportamiento estándar de diferentes maneras. Las secciones que se incluyen a continuación describen algunas de otras personalizaciones comunes.

Cómo modificar el diseño

El objeto RecyclerView usa un administrador de diseño para posicionar los elementos individuales en la pantalla y determinar cuándo volver a usar las vistas de elementos que ya no ve el usuario. Para volver a usar (o reciclar) una vista, un administrador de diseño puede solicitar al adaptador que reemplace el contenido de la vista por un elemento diferente del conjunto de datos. Reciclar la vista de esta forma mejora el rendimiento evitando que se creen vistas innecesarias o realizando búsquedas de findViewById() con un alto costo. La Biblioteca de compatibilidad de Android incluye tres administradores de diseño estándar, cada uno de los cuales ofrece muchas opciones de personalización:

Si ninguno de estos administradores de diseño se adecua a tus necesidades, puedes crear uno propio extendiendo la clase abstracta RecyclerView.LayoutManager.

Cómo agregar animaciones de elementos

Siempre que cambie un elemento, el objeto RecyclerView usa un animador para cambiar su apariencia. Este animador es un objeto que extiende la clase abstracta RecyclerView.ItemAnimator. De forma predeterminada, el objeto RecyclerView usa DefaultItemAnimator para proporcionar la animación. Si quieres proporcionar animaciones personalizadas, puedes definir tu propio objeto animador extendiendo RecyclerView.ItemAnimator.

Cómo habilitar la selección de elementos de listas

La biblioteca de recyclerview-selection permite a los usuarios seleccionar elementos en la lista RecyclerView con entrada táctil o del mouse. Tú sigues teniendo el control de la presentación visual de un elemento seleccionado. También puedes seguir controlando las políticas que rigen el comportamiento de selección, como los elementos que pueden seleccionarse y la cantidad de estos.

Para agregar compatibilidad de selección a una instancia de RecyclerView, sigue los pasos que se describen a continuación:

  1. Determina el tipo de clave de selección que usarás y, luego, crea un objeto ItemKeyProvider.

    Hay tres tipos de claves que puedes usar para identificar elementos seleccionados: Parcelable (y todas las subclases como Uri), String y Long. Para obtener información detallada sobre los tipos de claves de selección, consulta SelectionTracker.Builder.

  2. Implementa ItemDetailsLookup.
  3. ItemDetailsLookup permite que la biblioteca de selección acceda a información sobre elementos RecyclerView a los que se otorga MotionEvent. Se trata efectivamente de una fábrica de instancias de ItemDetails que están respaldadas por una instancia de RecyclerView.ViewHolder (o se extraen de ella).

  4. Actualiza el elemento Views de RecyclerView para que refleje si el usuario lo seleccionó o no.

    La biblioteca de selección no proporciona una decoración visual predeterminada de los elementos seleccionados. Debes proporcionarla cuando implementes onBindViewHolder(). El enfoque recomendado es el siguiente:

  5. Usa ActionMode a fin de ofrecer al usuario las herramientas para llevar a cabo una acción sobre la selección.
  6. Registra un objeto SelectionTracker.SelectionObserver para recibir una notificación sobre un cambio de selección. Cuando se crea una selección por primera vez, abre ActionMode para representar esto ante el usuario y proporciona acciones específicas de la selección. Por ejemplo, puedes agregar un botón de eliminación a la barra de ActionMode y conectar la flecha hacia atrás de la barra para borrar la selección. Cuando se vacía la selección (si el usuario borró la selección la última vez), no olvides finalizar el modo de acción.

  7. Cómo realizar acciones secundarias interpretadas
  8. Al final de la canalización del procesamiento de eventos, la biblioteca puede determinar que el usuario intenta activar un elemento al presionarlo o que intenta arrastrar y soltar un elemento o conjunto de elementos seleccionados. Registra el objeto de escucha correspondiente como reacción a estas interpretaciones. Para obtener más información, consulta SelectionTracker.Builder.

  9. Reúne todo con SelectionTracker.Builder
  10. El ejemplo que se incluye a continuación muestra cómo reunir todo esto usando la clave de selección Long:

    Kotlin

        var tracker = SelectionTracker.Builder(
            "my-selection-id",
            recyclerView,
            StableIdKeyProvider(recyclerView),
            MyDetailsLookup(recyclerView),
            StorageStrategy.createLongStorage())
                .withOnItemActivatedListener(myItemActivatedListener)
                .build()
        

    Java

        SelectionTracker tracker = new SelectionTracker.Builder<>(
                "my-selection-id",
                recyclerView,
                new StableIdKeyProvider(recyclerView),
                new MyDetailsLookup(recyclerView),
                StorageStrategy.createLongStorage())
                .withOnItemActivatedListener(myItemActivatedListener)
                .build();
        

    Para crear una instancia de SelectionTracker, tu app debe proporcionar el mismo objeto RecyclerView.Adapter que usaste para iniciar RecyclerView en SelectionTracker.Builder. Es por esto que probablemente necesitarás inyectar la instancia de SelectionTracker, una vez creada, en el objeto RecyclerView.Adapter después de crear RecyclerView.Adapter. De lo contrario, no podrás verificar el estado seleccionado de un elemento con el método onBindViewHolder().

  11. Incluye la selección en los eventos del ciclo de vida de la actividad.
  12. Para preservar el estado de selección entre los eventos del ciclo de vida de la actividad, tu app debe llamar a los métodos onSaveInstanceState() y onRestoreInstanceState() del seguimiento de selección de los métodos onSaveInstanceState() y onRestoreInstanceState() de la actividad, respectivamente. Tu app también debe proporcionar un ID de selección único al constructor SelectionTracker.Builder. Este ID es necesario porque una actividad o un fragmento pueden tener más de una lista distinta para seleccionar, y todas deben tener un estado persistente guardado.

    Recursos adicionales

    RecyclerView se usa en la app de demostración de Sunflower.