Android RecyclerView: Qué es, aprenda con ejemplos sencillos

⚡ Resumen inteligente

RecyclerView es una herramienta avanzada Android Widget que muestra conjuntos de datos grandes y desplazables de forma eficiente mediante el reciclaje de un número limitado de vistas. Se basa en un adaptador, un gestor de diseño y un contenedor de vistas para vincular colecciones de datos que cambian en tiempo de ejecución.

  • ???? Qué es: RecyclerView es un sucesor flexible de ListView y GridView para mostrar grandes colecciones de datos.
  • 🧱 Vistas y grupos de vistas: Android Dibuja la interfaz utilizando las clases View y ViewGroup, con LayoutParams controlando el tamaño y la posición.
  • 🔄 Adaptador: El patrón adaptador convierte una matriz de datos en ViewHolders que el RecyclerView puede aceptar.
  • 📐 Administrador de diseño: El gestor de diseño organiza los elementos de forma lineal o en una cuadrícula e indica cuándo se pueden reutilizar las vistas.
  • ♻️ Reciclaje: Las vistas que quedan fuera de pantalla se reutilizan en lugar de recrearse, lo que ahorra memoria y mantiene un desplazamiento fluido.
  • 🛠️ Pasos de construcción: Agregar el formulario Gradle dependencia, crea un diseño de elemento, un ViewHolder, un adaptador y un LayoutManager.

Android RecicladorVer

¿Qué es RecyclerView en Android?

El RecicladorVer Es una versión más flexible y avanzada de GridView y ListView. Es un contenedor para mostrar grandes conjuntos de datos que se pueden desplazar de forma eficiente manteniendo un número limitado de vistas. Puedes usar el widget RecyclerView cuando tengas colecciones de datos cuyos elementos cambien en tiempo de ejecución según un evento de red o una acción del usuario.

Vistas

El Android La plataforma utiliza las clases View y ViewGroup para dibujar elementos en la pantalla. Estas clases son absolutas.tracLas clases `TextView` y `EditText` se extienden a diferentes implementaciones para adaptarse a cada caso de uso. Por ejemplo, `TextView` tiene el simple propósito de mostrar texto en la pantalla. `EditText` hereda de la misma clase `View` y añade más funcionalidades para permitir al usuario introducir datos.

Es posible crear nuestras propias vistas personalizadas para lograr mayor flexibilidad al desarrollar.ping interfaces de usuario. La clase View proporciona métodos que podemos sobrescribir para dibujar en la pantalla y una forma de pasar parámetros como ancho, alto y nuestros propios atributos personalizados que queramos agregar a nuestra View para que se comporte como deseamos.

Ver grupos

La clase ViewGroup es un tipo de Vista, pero, a diferencia de la clase View simple, cuya única función es mostrar información, ViewGroup nos permite agrupar varias vistas en una sola, a la que podemos hacer referencia como un todo. En este caso, la Vista creada en el nivel superior, a la que añadimos otras vistas simples, se denomina "padre", y las vistas que se añaden en su interior se denominan "hijos".

Podemos imaginar una Vista como una Matriz y un Grupo de Vistas como una Matriz de Matriz. Dado que una Matriz de Matriz es una Matriz en sí misma, podemos ver cómo un Grupo de Vistas puede tratarse como una Vista.

var arr1 = [1,2,3] //imagine a simple View as an Array
//we can imagine this as a NumberTextView which doesn't really exist
//but we could imagine there's one that makes it easy to use numbers
var arr2 = ["a","b","c"] // We can imagine this as another simple view

var nestedArr = [arr1,arr2] //in our analogy, we can now group views
//together and the structure that would hold that would be what we call the ViewGroup

El ViewGroup también nos permite definir cómo se organizan los elementos secundarios dentro de la vista, por ejemplo, si se disponen vertical u horizontalmente. Podemos establecer diferentes reglas de interacción dentro de la vista. Por ejemplo, dos TextView consecutivos deben tener una distancia de 12 dp, mientras que un ImageView seguido de un TextView debe tener una distancia de 5 dp.

Este sería el caso si estuviéramos desarrollandoping nuestro propio ViewGroup desde cero. Para facilitar estas configuraciones, Android proporciona una clase llamada LayoutParams, que podemos usar para ingresar estas configuraciones.

El Android documentación Proporciona algunos parámetros predeterminados que implementaremos al configurar nuestro propio ViewGroup. Algunos parámetros comunes se refieren al ancho, la altura y el margen. Por defecto, estas configuraciones tienen la estructura android:layout_height para la altura y android:layout_width para el ancho. En este sentido, al crear su ViewGroup, puede crear LayoutParams específicos para el comportamiento que desea que tenga.

Android Incluye vistas y grupos de vistas predeterminados que podemos usar para realizar muchas de las tareas comunes. Un ejemplo que ya mencionamos es un TextView. Se trata de una vista sencilla con aspectos configurables como la altura, el ancho y el tamaño del texto. También contamos con un ImageView para mostrar imágenes y un EditText, como ya mencionamos, entre muchos otros. Android También tiene grupos de vistas personalizados a los que podemos agregar nuestras vistas y obtener el comportamiento esperado.

diseño lineal

LinearLayout nos permite agregar elementos View. LinearLayout tiene un atributo de orientación que determina cómo se mostrará en la pantalla. También tiene LinearLayout.LayoutParams que definen las reglas para las vistas internas. Por ejemplo, el atributo android:center_horizontal centra las vistas en el eje horizontal, mientras que android:center_vertical centra el contenido de la vista en el eje vertical.

Aquí hay algunas imágenes para ayudar a comprender el centrado. Supongamos que se trata de un TextView simple dentro de un espacio de 200px por 200px; los atributos de centrado harían que se comportara de la siguiente manera.

android:centro_horizontal

Contenido centrado horizontalmente

Contenido centrado horizontalmente

Android:center_vertical

Contenido centrado verticalmente

Contenido centrado verticalmente

android:centro

Contenido centrado

Contenido centrado

Componentes principales de RecyclerView

Componentes principales de RecyclerView

Componentes principales de RecyclerView

Los siguientes son los componentes importantes de RecyclerView:

RecyclerView.Adaptador

Trabajo preliminar n.º 1: patrón de adaptador

Un adaptador es un dispositivo que transforma las características de un sistema o dispositivo para adaptarlas a las de otro sistema o dispositivo que, de otro modo, sería incompatible. Algunos adaptadores modifican las características de la señal o la alimentación, mientras que otros simplemente adaptan la forma física de un conector a otro.

Un ejemplo sencillo de la vida real que ilustra la utilidad de un adaptador es cuando necesitamos conectar dispositivos, pero sus puertos de conexión no son compatibles. Esto podría ocurrir al visitar un país con enchufes diferentes. Si llevas el cargador del teléfono o del portátil, sería imposible conectarlo a la toma de corriente. Sin embargo, no te darías por vencido y simplemente usarías un adaptador que conecta la toma de corriente con el cargador, permitiendo así la carga.

Este es el caso en programación cuando queremos conectar dos estructuras de datos para realizar una tarea, pero sus puertos predeterminados no tienen forma de comunicarse entre sí.

Usaremos el ejemplo sencillo de un dispositivo y un cargador. Tendremos dos cargadores: uno estadounidense y otro británico.

class AmericanCharger() {
    var chargingPower = 10
}
class BritishCharger(){
    var chargingPower = 5
}

A continuación, crearemos dos dispositivos.

class AmericanDevice()
class BritishDevice()

Como ejemplo, podemos crear algunas instancias de los dispositivos para seguir el juego.

var myAmericanPhone = new AmericanDevice()
var myBritishPhone = new BritishDevice()

A continuación, introduciremos el concepto de carga para ambos dispositivos añadiendo un método llamado `charge()`. Este método recibe como parámetro su cargador correspondiente y realiza la carga en función de él.

sealed trait Device
class AmericanDevice : Device{
    fun charge(charger:AmericanCharger){
        //Do some American charging
    }
}
class BritishDevice: Device{
    fun charge(charger:BritishCharger){
        //Do some British charging
    }
}

En este caso, basándonos en nuestra analogía, por una razón u otra necesitaríamos usar un cargador británico al usar un dispositivo estadounidense, o viceversa.

En el mundo de la programación, esto suele ocurrir cuando se mezclan bibliotecas que ofrecen la misma funcionalidad (en nuestro contexto, nuestra funcionalidad compartida es cobrar). Tendríamos que encontrar una manera de permitir esto.

Siguiendo la analogía, tendríamos que ir a una tienda de electrónica y comprar un adaptador que nos permita cargar dispositivos estadounidenses con cargadores británicos. Desde el punto de vista de la programación, nosotros seríamos los fabricantes del adaptador.

Crearemos un adaptador que coincida exactamente con el patrón que necesitaríamos para crear el otro. Lo implementaremos como una clase de la siguiente manera. No necesariamente tiene que ser una clase y podría ser una función que destaque lo que generalmente hace el patrón adaptador. Usamos una clase porque coincide con la mayoría de los usos en Android.

class AmericanToBritishChargerAdapter(theAmericanCharger:AmericanCharger){
    fun returnNewCharger(): BritishCharger{
            //convert the American charger to a BritishCharger
            //we would change the American charging functionality
            //to British charging functionality to make sure the
            //adapter doesn't destroy the device. The adapter could
            //, for example, control the power output by dividing by 2
            //our adapter could encompass this functionality in here

           var chargingPower:Int = theAmericanCharger.chargingPower / 2
           var newBritishCharger = new BritishCharger()
           newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2

        return newBritishCharger
    }
}

En el mundo de la programación, la diferencia entre los enchufes es análoga a la diferencia en los métodos de carga. Si los cargadores tuvieran métodos diferentes, sería imposible utilizarlos.

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()

Intentar llamar al método charge() en myBritishDevice con americanChargerIFound no funcionaría, ya que AmericanDevice solo acepta un AmericanCharger. Por lo tanto, es imposible hacer esto:

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
myBritishDevice.charge(americanChargerIFound)

En este caso, el adaptador que creamos, AmericanToBritishChargerAdapter, nos será muy útil. Podemos usar el método returnNewCharger() para crear un nuevo BritishCharger, que podremos usar para cargar. Solo necesitamos crear una instancia de nuestro adaptador y proporcionarle el AmericanCharger que tenemos; así creará un BritishCharger que podremos usar.

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
//We create the adapter and feed it the americanCharger
var myAdapter =  AmericanToBritishChargerAdapter(americanChargerIFound)
//calling returnNewCharger from myAdapter would return a BritishCharger
var britishChargerFromAdapter = myAdapter.returnNewCharger()
//and once we have the britishCharger we can now use it
myBritishDevice.charge(britishChargerFromAdapter)

RecicladorView.LayoutManager

Cuando trabajamos con un ViewGroup, tenemos vistas ubicadas dentro de él. El LayoutManager se encarga de describir cómo se distribuyen las vistas en su interior.

A modo de comparación, cuando trabajamos con un ViewGroup de LinearLayout, el caso de uso que queremos es la capacidad de colocar los elementos vertical u horizontalmente. Esto se implementa fácilmente agregando un atributo de orientación, que nos indica cómo se colocará el LinearLayout en la pantalla. Podemos hacerlo utilizando el android:orientation=VERTICAL|HORIZONTAL atributo.

También contamos con otro ViewGroup llamado GridLayout. Su utilidad radica en la organización de vistas en una estructura de cuadrícula rectangular. Esto puede deberse, por ejemplo, a la necesidad de facilitar la comprensión de los datos que se presentan al usuario de la aplicación. Por diseño, GridLayout permite configuraciones que facilitan la definición de las dimensiones de la cuadrícula; por ejemplo, podemos tener una cuadrícula de 4x4 o de 3x2.

RecicladorView.ViewHolder

El ViewHolder es un abstracclase t que también extendemos de RecyclerView. El ViewHolder nos proporciona métodos comunes para ayudarnos a hacer referencia a una Vista que hemos colocado en el RecyclerView, incluso después de que el mecanismo de reciclaje en el RecyclerView haya cambiado varias referencias que desconocemos.

Listas grandes

Los RecyclerViews se utilizan cuando queremos presentar un conjunto realmente grande de vistas al usuario, sin agotar el RAM en nuestro dispositivo para todas y cada una de las instancias de la Vista creada.

Si tomamos como ejemplo una lista de contactos, tendríamos una idea general de cómo se vería un contacto en la lista. Lo que haríamos entonces es crear una plantilla de diseño, que en realidad es una Vista, con espacios donde se rellenarán los distintos datos de nuestra lista de contactos. El siguiente es un pseudocódigo que explica todo el proceso:

//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>

Tendríamos entonces una lista de contactos de esta naturaleza:

<ContactList>
</ContactList>

Si estuviéramos codificando el contenido de forma fija, no tendríamos una forma programática de agregar nuevo contenido a la lista sin reescribir la aplicación. Afortunadamente para nosotros, agregar una Vista a un ViewGroup es compatible con un addView(view:View) método. Aun así, no es así como se agregan las vistas secundarias al RecyclerView.

En nuestro caso de uso, tendríamos una larga lista de contactos. Para cada contacto de la lista, necesitaríamos crear una vista OneContactView y completar los datos dentro de la vista para que coincidan con los campos de nuestra clase Contact. Luego, una vez que tengamos la vista, tendríamos que agregarla al RecyclerView para mostrar la lista.

data  class Contact(var name:String, var address:String, var pic:String, var phoneNumber:Int)

var contact1 = Contact("Guru","Guru97", "SomePic1.jpg", 991)
var contact2 = Contact("Guru","Guru98", "SomePic2.jpg", 992)
var contact3 = Contact("Guru","Guru99", "SomePic3.jpg", 993)

var myContacts:ArrayList<Contact> = arrayListOf<Contact>(contact1,contact2,contact3)

Tenemos una matriz de contactos. OneContactView contiene ranuras para tomar el contenido de la clase Contact y mostrarlo. En RecyclerView, debemos agregarle vistas para que nos ayude con su capacidad de reciclaje.

El RecyclerView en realidad no nos permite agregar una vista, sino que nos permite agregar un ViewHolder. Entonces, en este escenario, tenemos dos piezas que queremos conectar pero que no coinciden. Aquí es donde entra nuestro adaptador. RecyclerView nos proporciona un adaptador, muy parecido a nuestro AmericanToBritishChargerAdapter() Gracias a lo anterior, pudimos convertir nuestro AmericanCharger, que era inutilizable con nuestro BritishDevice, en algo utilizable, similar a un adaptador de corriente en la vida real.

En este caso, el adaptador tomaría nuestra matriz de contactos y nuestra vista, y a partir de ahí generaría ViewHolders que el RecyclerView esté dispuesto a aceptar.

RecyclerView proporciona una interfaz que podemos extender para crear nuestro adaptador mediante la clase RecyclerView.Adapter. Dentro de este adaptador se encuentra la forma de crear la clase ViewHolder con la que RecyclerView desea trabajar. Por lo tanto, tenemos la misma situación que antes, pero con un elemento adicional: el adaptador.

Tenemos un array de contactos, una vista para mostrar un contacto (OneContactView) y un RecyclerView, que es una lista de vistas que ofrece servicios de reciclaje, pero que solo acepta ViewHolders. En este caso, ahora tenemos una clase RecyclerView.Adapter, que contiene un método para crear ViewHolders.

fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder

El RecyclerView.ViewHolder es un abstracClase que toma nuestra Vista como argumento y la convierte en un ViewHolder. Utiliza el patrón de envoltura que se usa para extender las capacidades de las clases.

Trabajo preliminar n.° 2: patrón de envoltura

Usaremos un ejemplo sencillo para demostrar cómo podemos hacer hablar a los animales.

sealed trait Animal{
    fun sound():String
}

data class Cat(name:String):Animal{
    fun sound(){
        "Meow"
        }
}
data class Dog(name:String):Animal{
    fun sound(){
        "Woof"
        }
}

var cat1 = Cat("Tubby")
var dog1 = Dog("Scooby")
cat1.sound() //meow
dog1.sound() //woof

En el ejemplo anterior, tenemos dos animales. Si, por casualidad, quisiéramos añadir un método para que hablaran, pero el autor de la biblioteca no fuera muy amable, aún podríamos encontrar una solución. Necesitaríamos una clase contenedora para nuestra clase Animal. Para ello, pasaríamos la clase Animal como constructor de nuestra clase.

class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

Ahora podemos pasar una instancia de animal a SpeechPoweredAnimalByWrapper. Al llamar al método sound() en ella, se llamará al método sound() del animal pasado como parámetro. También tenemos un método speak() adicional, que cuenta como una nueva funcionalidad que agregamos a los animales pasados ​​como parámetro. Podemos usarlo de la siguiente manera:

var cat1 =  Cat("Garfield")
cat1.sound()//"meow"
cat1.speak()// doesn't work as it isn't implemented
var talkingCat = new SpeechPoweredAnimalByWrapper(cat1)
talkingCat.sound() //"meow" the sound method calls the one defined for cat1
talkingCat.speak() //"Hello, my name is Garfield"

Utilizando este patrón, podemos tomar clases y agregarles funcionalidad. Todo lo que necesitamos es pasar una instancia de clase y los nuevos métodos definidos por nuestro envoltorio.ping clase.

En el caso anterior, utilizamos una clase concreta. También es posible implementar lo mismo en un abs.tracclase t. Necesitaríamos cambiar la clase SpeechPoweredAnimalByWrapper a abstract, y hemos terminado. Cambiaremos el nombre de la clase por uno más corto para que sea más legible.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

Es lo mismo que antes, pero significaría algo diferente. En una clase normal, podemos tener una instancia de una clase de la misma manera que creamos cat1 y dog1.tracSin embargo, las clases t no están diseñadas para ser instanciadas, sino para extender otras clases. Entonces, ¿cómo usaríamos el nuevo SpeechPowered(var myAnimal:Animal)?trac¿Clase t? Podemos usarla creando nuevas clases que la extiendan y, a su vez, obtengan su funcionalidad.

En nuestro ejemplo, crearemos una clase SpeechPoweredAnimal que extiende la clase.

class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 =  Cat("Tubby")
var speakingKitty = SpeechPoweredAnimal(cat1)
speakingKitty.speak() //"Hello, my name is Tubby"

Este es el mismo patrón utilizado en el ViewHolder. La clase RecyclerView.ViewHolder es un abstracLa clase añade funcionalidad a la vista, de forma similar a como añadimos el método speak a los animales. Esta funcionalidad añadida es lo que permite su funcionamiento al interactuar con RecyclerView.

Así es como crearíamos un OneContactViewHolder a partir de OneContactView:

//The View argument we pass is converted to a ViewHolder which uses the View to give it more abilities and in turn work with the RecyclerView
class OneContactViewHolder(ourContactView: View) : RecyclerView.ViewHolder(ourContactView)

RecyclerView tiene un adaptador que nos permite conectar nuestro array de contactos a la vista ContactsView dentro del RecyclerView.

Agregar una vista

El ViewGroup no se actualiza automáticamente, sino que sigue un ciclo determinado. En tu dispositivo, puede que se actualice cada 10 ms o 100 ms; o, si elegimos un valor absurdo, por ejemplo, 1 minuto, al añadir una vista a un ViewGroup, verás los cambios 1 minuto después, cuando el ViewGroup se actualice.

RecicladorView.Recycler

Preparación del terreno n.º 3: Almacenamiento en caché

Uno de los mejores ejemplos de dónde actualizamos la página con frecuencia es en el navegador. Imaginemos, por ejemplo, que el sitio que visitamos es estático y no envía contenido dinámicamente; necesitaríamos actualizar la página constantemente para ver los cambios.

Para este ejemplo, imaginemos que el sitio en cuestión es Twitter. Tendríamos una serie de tweets estáticos listados, y la única forma de ver tweets nuevos sería haciendo clic en el botón de actualizar para volver a obtener el contenido.

Obviamente, actualizar toda la pantalla es costoso. Imagínese si tuviéramos un ancho de banda limitado con nuestro proveedor de telefonía y nuestra lista de tweets tuviera muchas imágenes y videos; sería costoso volver a descargar todo el contenido de la página cada vez que se actualice.

Necesitaríamos una forma de almacenar los tweets ya cargados y asegurarnos de que nuestra próxima solicitud pueda indicar qué tweets ya tiene. Por lo tanto, no se vuelve a descargar todo, sino que solo se obtienen los tweets nuevos, y también se comprueba si algún tweet guardado localmente ya no está disponible para poder eliminarlo. Lo que describimos se llama almacenamiento en caché.

La información que enviamos al sitio web sobre el contenido que tenemos se llama metadatos. Así que, en realidad, no solo decimos "queremos cargar tu sitio", sino que decimos "queremos cargar tu sitio y aquí tienes parte del contenido que ya guardamos de la última vez que lo cargamos; por favor, úsalo para enviarnos solo lo que no esté ahí, para que no consumamos mucho ancho de banda".

Llamadas de diseño: la lista de tweets debe ser una locura

Un ejemplo de llamada de diseño es scrollToPosition. Este es un ejemplo común en aplicaciones de chat. Si alguien en una conversación responde a una burbuja de chat anterior, algunas aplicaciones incluyen la respuesta y un enlace a la burbuja de chat, que al hacer clic te lleva a donde estaba tu mensaje original.

En el caso de que llamemos a este método antes de que hayamos agregado un LayoutManager a nuestro RecyclerView y antes de que tengamos un RecyclerView.Adapter, scrollToPosition(n:Int) simplemente se ignora.

Comunicación entre componentes de RecyclerView

Trabajo preliminar n.° 4: Devoluciones

El RecyclerView, en su funcionamiento, tiene muchos componentes. Debe interactuar con el LayoutManager, que nos indica cómo organizar las vistas, ya sea linealmente o en una cuadrícula. También debe interactuar con un adaptador que se encarga de convertir nuestros elementos (contactList) en vistas (OneContactView) y luego en ViewHolders (OneContactViewHolder) con los que el RecyclerView puede trabajar utilizando los métodos que proporciona.

La materia prima para RecyclerView son nuestras vistas, por ejemplo OneContactView, y una fuente de datos.

contactList:Array<Contact>

Hemos utilizado un escenario sencillo como punto de partida para comprender el funcionamiento de RecyclerView. Un caso básico, cuando tenemos una lista estática de 1000 contactos que queremos mostrar al usuario, es fácil de entender. El funcionamiento de RecyclerView cobra vida cuando la lista deja de ser estática. Con una lista dinámica, debemos considerar qué sucede con la vista en pantalla cuando añadimos o eliminamos un elemento de la lista.

RecicladorView.LayoutManager

Además de decidir cómo se organizan nuestras vistas, ya sea linealmente o en una cuadrícula, el LayoutManager realiza mucho trabajo en segundo plano para ayudar aping El reciclador sabe cuándo reciclar.

Es responsable de mantenerping track de las vistas actualmente visibles en la pantalla y comunica esta información al mecanismo de reciclaje. A medida que el usuario se desplaza hacia abajo, el LayoutManager se encarga de informar al sistema de reciclaje sobre las vistas que pierden el foco en la parte superior para que puedan reutilizarse, en lugar de permanecer allí y consumir memoria o ser destruidas solo para crear otras nuevas.

Esto significa que el LayoutManager necesita mantener track de la posición del usuario mientras se desplaza por nuestra lista. Esto se logra mediante una lista de posiciones indexadas, es decir, el primer elemento comienza en 0 y aumenta para coincidir con el número de elementos en nuestra lista.

Si podemos ver 10 elementos de nuestra lista de, digamos, 100, al principio el Administrador de diseño sabe que tiene en foco desde la Vista 0 hasta la Vista 9. A medida que nos desplazamos, el Administrador de diseño puede calcular las vistas que pierden el foco.

El LayoutManager puede liberar estas vistas al mecanismo de reciclaje para que puedan reutilizarse (se les pueden vincular nuevos datos; por ejemplo, se pueden eliminar los datos de contacto de una vista y reemplazar los marcadores de posición con nuevos datos de contacto del siguiente segmento).

Esto sería ideal si la lista fuera estática, pero uno de los usos más comunes de RecyclerView es con listas dinámicas, donde los datos provienen de un servidor en línea o incluso de un sensor. No solo se agregan datos, sino que a veces también se eliminan o actualizan.

El estado dinámico de nuestros datos puede dificultar enormemente el análisis del LayoutManager. Por este motivo, el LayoutManager mantiene su propia lista de elementos y posiciones, independiente de la lista que utiliza el componente de reciclaje. Esto garantiza que realice su tarea de diseño correctamente.

Al mismo tiempo, el LayoutManager de RecyclerView no quiere tergiversar los datos que contiene. Para funcionar correctamente, el LayoutManager se sincroniza con RecyclerView.Adapter a intervalos regulares (60 ms), compartiendo información sobre los elementos de la lista: elementos añadidos, actualizados, eliminados o movidos de posición. Al recibir esta información, el LayoutManager reorganiza el contenido de la pantalla para reflejar los cambios cuando sea necesario.

Muchas de las operaciones principales relacionadas con RecyclerView giran en torno a la comunicación entre RecyclerView.LayoutManager y RecyclerView.Adapter, que almacena nuestras listas de datos, a veces estáticos y a veces dinámicos.

Además, RecyclerView nos ofrece métodos que podemos usar para escuchar eventos, como onBindViewHolder, cuando nuestro RecyclerView.Adapter enlaza contenido de nuestra lista (por ejemplo, un contacto) a un ViewHolder para que se utilice para mostrar la información en pantalla.

Otro ejemplo es onCreateViewHolder, que nos indica cuándo RecyclerView.Adapter toma una vista normal como OneContactView y la convierte en un elemento ViewHolder con el que RecyclerView puede trabajar.

Además de los mecanismos básicos que permiten el reciclaje, RecyclerView ofrece formas de personalizar el comportamiento sin afectar el reciclaje. La reutilización de vistas dificulta realizar acciones comunes a las que estamos acostumbrados con vistas estáticas, como reaccionar a eventos onClick.

Como sabemos, el RecyclerView.LayoutManager que presenta las Vistas al usuario podría tener por un momento una lista de elementos diferente de la RecyclerView.Adapter que tiene la lista almacenada en una base de datos o transmitida desde una fuente. Colocar eventos OnClick directamente en las vistas puede provocar un comportamiento inesperado, como eliminar el contacto incorrecto.

Gradle

Si queremos usar RecyclerView, debemos agregarlo como dependencia en nuestro archivo build.gradle. En el siguiente ejemplo, hemos usado la implementación “androidx.recyclerview:recyclerview:1.1.0”, que es la versión más reciente según este artículo.

Después de agregar la dependencia a nuestro Gradle archivo, se nos solicitará Android Studio para sincronizar los cambios. Así es como nuestro Gradle El archivo se verá después de agregar un RecyclerView en un proyecto vacío con solo los valores predeterminados.

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.guru99.learnrecycler"
        minSdkVersion 17
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
             'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    implementation "androidx.recyclerview:recyclerview:1.1.0"
}

Por el momento, solo disponemos de un archivo de diseño. Comenzaremos con un ejemplo sencillo en el que utilizaremos un RecyclerView para mostrar una lista de nombres de frutas en la pantalla.

Lista de artículos

Navegaremos hasta nuestro archivo MainActivity y crearemos una matriz con los nombres de las frutas, justo antes del método onCreate() que se generó durante la configuración.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

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

Nuestro siguiente objetivo será presentar esta lista en pantalla utilizando un RecyclerView. Para ello, navegaremos al directorio de diseños que contiene nuestros diseños y crearemos una vista que se encargará de mostrar una fruta.

Diseño que se utilizará para cada elemento de nuestra lista.

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/fruitName"
    />
</TextView>

En el TextView anterior, hemos añadido un campo de ID que se utilizará para identificar una Vista. Este campo no se genera automáticamente. Le hemos asignado a nuestro TextView el ID fruitName para que coincida con los datos que se vincularán a él.

Agregar RecyclerView al diseño principal

En la misma actividad, hay un archivo de diseño main_layout.xml que se generó para nosotros por defecto. Si elegimos un proyecto vacío, se habrá generado un XML Contiene un ConstraintLayout y dentro habrá un TextView con el texto “Hola”. Eliminaremos todo el contenido y haremos que el diseño contenga únicamente el RecyclerView, como se muestra a continuación:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/fruitRecyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

También hemos añadido un atributo id para el RecyclerView, que utilizaremos para hacer referencia a él en nuestro código.

android:id="@+id/fruitRecyclerView"

Luego, volveremos a nuestro archivo MainActivity. Usando los ids que hemos creado, podremos hacer referencia a las vistas que acabamos de crear. Comenzaremos haciendo referencia al RecyclerView usando el método findViewById() proporcionado por Android. Haremos esto en nuestro método onCreate(). Nuestro método onCreate() se verá así.

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

    var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
}

Crear un titular de vista

A continuación, crearemos un RecyclerView.ViewHolder, que se encargará de tomar nuestra Vista y convertirla en un ViewHolder, que el RecyclerView utilizará para mostrar nuestros elementos. Haremos esto justo después de nuestro método onCreate().

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

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

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

}

Crear un RecyclerViewAdapter

A continuación, crearemos una clase FruitArrayAdapter que extiende la clase RecyclerView.Adapter. El FruitArrayAdapter que crearemos se encargará de lo siguiente: tomará los nombres de las frutas del array de frutas, creará un ViewHolder usando nuestra vista one_fruit_view.xml, luego vinculará la fruta a un ViewHolder y vinculará dinámicamente el contenido a la vista que creamos.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

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

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitArrayAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>()
}

Android Studio agregará líneas onduladas rojas a nuestro FruitArrayAdapter, lo que nos indica que necesitamos implementar métodos que el RecyclerView pueda usar para conectar nuestro array a un ViewHolder.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

     }

     override fun getItemCount(): Int {

     }

     override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

     }
 }

Comenzaremos con la parte más sencilla del código generado, el método getItemCount(). Sabemos cómo obtener el número de elementos en nuestro array llamando a la propiedad de tamaño del array.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

        }

        override fun getItemCount(): Int {
         return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

A continuación, implementaremos el método onCreateViewHolder. Aquí es donde RecyclerView nos pide que le ayudemos a construir un FruitViewHolder. Si recordamos, así era como se veía nuestra clase FruitViewHolder:

class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

Requiere nuestra vista de fruta, que creamos como un archivo XML llamado one_fruit_view.xml. Podemos crear una referencia a este XML y convertirlo en una vista de la siguiente manera.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder

        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

La parte restante es la anulación de onBindViewHolder.

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

El RecyclerView.Adapter solicita una posición entera, que utilizamos para obtener un elemento de nuestra lista. También nos proporciona un contenedor para que podamos vincular el elemento obtenido del fruitArray a la vista que se encuentra dentro del ViewHolder. La vista contenida en un ViewHolder es accesible a través del campo ViewHolder.itemView. Una vez que obtenemos la vista, podemos usar el ID fruitName que creamos anteriormente para establecer el contenido.

override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

          var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

          var aFruitName = fruitArray.get(position)

          ourFruitTextView.setText(aFruitName)
      }

Con esto, nuestro FruitArrayAdapter está completo y tiene el siguiente aspecto.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }

Finalmente, estamos listos para conectar las piezas restantes de nuestro RecyclerView, que consisten en crear un LayoutManager que le indicará al RecyclerView cómo mostrar el contenido de la lista, ya sea de forma lineal usando LinearLayoutManager o en una cuadrícula usando GridLayoutManager o StaggeredGridLayoutManager.

Crear administrador de diseño

Volveremos a entrar en nuestra función onCreate y añadiremos el LayoutManager.

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

       var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

       var fruitLinearLayout = LinearLayoutManager(this)

       myFruitRecyclerView.layoutManager =fruitLinearLayout

   }

Conecte nuestro adaptador a los artículos y configúrelo en RecyclerView

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

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

También hemos creado una instancia de fruitListAdapter y le hemos proporcionado el array de nombres de frutas. Y básicamente, hemos terminado. El archivo completo MainActivity.kt tiene el siguiente aspecto.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

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

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }
}

Descarga el proyecto

Preguntas Frecuentes

RecyclerView es el sucesor más flexible y eficiente de ListView. Implementa el patrón ViewHolder, admite diseños lineales, de cuadrícula y escalonados mediante administradores de diseño, y añade animaciones de elementos integradas, mientras que ListView no ofrece ninguna de estas características por defecto.

GridView solo muestra elementos en una cuadrícula fija. RecyclerView puede reproducir una cuadrícula usando GridLayoutManager, pero también admite diseños lineales y escalonados, reciclaje de vistas y animaciones, lo que lo hace mucho más versátil que GridView.

Este ejemplo de RecyclerView está escrito en Kotlin, el lenguaje recomendado oficialmente por Google por la Android desarrollo. Las mismas API de RecyclerView, adaptador y administrador de diseño también funcionan en Java.

Adjunte un detector de clics dentro del ViewHolder o en onBindViewHolder, y luego expóngalo a través de una llamada de retorno de interfaz a la actividad o fragmento. Usar la posición del ViewHolder evita vincular los clics al elemento reciclado incorrecto.

DiffUtil es una clase de utilidad que calcula la diferencia entre dos listas y envía el conjunto mínimo de actualizaciones al adaptador. Permite realizar cambios dinámicos en las listas de forma eficiente y anima automáticamente las adiciones, eliminaciones y movimientos.

Jetpack Compose utiliza LazyColumn y LazyRow en lugar de RecyclerView para las nuevas pantallas. Sin embargo, RecyclerView sigue siendo muy utilizado en proyectos basados ​​en View y puede integrarse en Compose mediante API de interoperabilidad.

Sí. Los asistentes de IA, como GitHub Copilot y las herramientas basadas en GPT, pueden generar una plantilla básica para el adaptador de RecyclerView, ViewHolder y onBindViewHolder a partir de una breve solicitud. Aun así, los desarrolladores deben revisar el código generado para comprobar su corrección y rendimiento.

Aprendizaje automático Android Las aplicaciones utilizan RecyclerView para mostrar resultados dinámicos como recomendaciones, clasificaciones de búsqueda o clasificaciones de imágenes. El adaptador vincula los resultados del modelo a los elementos de la lista, de modo que los conjuntos de datos grandes y que cambian con frecuencia se desplazan sin problemas.

Resumir este post con: