Cómo usar RecyclerView para mostrar una lista desplazable

Si piensas en las apps que usas con más frecuencia en el teléfono, casi todas las tienen al menos una lista. La pantalla del historial de llamadas, la app de Contactos, tu app de redes sociales favorita… Todas muestran una lista de datos. Como se ve en la siguiente captura de pantalla, en algunas de estas apps se muestra una lista simple de palabras o frases, mientras que otras muestran elementos más complejos, como tarjetas con texto e imágenes. Independientemente de cuál sea el contenido, mostrar una lista de datos es una de las tareas de IU más comunes de Android.

cf10a913f9db0ee4.png

Para ayudarte a crear apps con listas, Android proporciona RecyclerView. RecyclerView está diseñado para ser muy eficiente, incluso con listas grandes, ya que puede reutilizar o reciclar las vistas que se desplazan fuera de la pantalla. Cuando se desplaza un elemento de la lista fuera de la pantalla, RecyclerView reutiliza la vista del siguiente elemento que se mostrará. Esto significa que el elemento se rellena con contenido nuevo que se desplaza hacia la pantalla. Este comportamiento de RecyclerView ahorra mucho tiempo de procesamiento y ayuda a las listas a desplazarse de forma más fluida.

En la secuencia que se muestra a continuación, puedes ver que se completó una vista con datos, ABC. Después de que la vista se desplace fuera de la pantalla, RecyclerView volverá a usar la vista para datos nuevos, XYZ.

dcf4599789b9c2a1.png

En este codelab, compilarás la app de Affirmations. Es una app sencilla que muestra diez afirmaciones positivas como texto en una lista de desplazamiento. Luego, en el codelab de seguimiento, irás un paso más allá; agregarás una imagen inspiradora a cada afirmación y perfeccionarás la IU de la app.

Requisitos previos

  • Crear un proyecto a partir de una plantilla en Android Studio
  • Agregar recursos de strings a una app
  • Definir un diseño en XML
  • Comprender las clases y la herencia en Kotlin (incluidas las clases abstractas)
  • Heredar de una clase existente y anular sus métodos
  • Usa la documentación disponible en developer.android.com para consultar las clases que proporciona el framework de Android.

Qué aprenderás

  • Cómo usar un RecyclerView para mostrar una lista de datos
  • Cómo organizar el código en paquetes
  • Cómo usar adaptadores con RecyclerView para personalizar el aspecto de un elemento de lista individual

Qué compilarás

  • Una app que muestra una lista de strings de afirmación con un RecyclerView

Requisitos

  • Una computadora que tenga instalado Android Studio 4.1 o una versión posterior

Cómo crear un proyecto de actividad vacía

Antes de crear un proyecto nuevo, asegúrate de utilizar Android Studio 4.1 o versiones posteriores.

  1. Inicia un nuevo proyecto de Kotlin en Android Studio con la plantilla Empty Activity.
  2. Ingresa Affirmations como Name de la app, com.example.affirmations como el Package name y elige API Level 19 como Minimum SDK.
  3. Haz clic en Finish para crear el proyecto.

El siguiente paso para crear la app de Affirmations es agregar recursos. Agregarás lo siguiente al proyecto:

  • Recursos de strings para mostrar como afirmaciones en la app
  • Una fuente de datos para proporcionar una lista de afirmaciones a la app

Cómo agregar strings de Affirmation

  1. En la ventana Project, abre app > res > values > strings.xml. Este archivo actualmente tiene un único recurso que es el nombre de la app.
  2. En strings.xml, agrega las siguientes afirmaciones como recursos de strings individuales. Asigna el nombre affirmation1, affirmation2, y así sucesivamente.

Texto de Affirmations

I am strong.
I believe in myself.
Each day is a new opportunity to grow and be a better version of myself.
Every challenge in my life is an opportunity to learn from.
I have so much to be grateful for.
Good things are always coming into my life.
New opportunities await me at every turn.
I have the courage to follow my heart.
Things will unfold at precisely the right time.
I will be present in all the moments that this day brings.

Cuando termines, el archivo strings.xml debería verse de la siguiente manera.

<resources>
    <string name="app_name">Affirmations</string>
    <string name="affirmation1">I am strong.</string>
    <string name="affirmation2">I believe in myself.</string>
    <string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
    <string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
    <string name="affirmation5">I have so much to be grateful for.</string>
    <string name="affirmation6">Good things are always coming into my life.</string>
    <string name="affirmation7">New opportunities await me at every turn.</string>
    <string name="affirmation8">I have the courage to follow my heart.</string>
    <string name="affirmation9">Things will unfold at precisely the right time.</string>
    <string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>

Ahora que agregaste recursos de strings, puedes hacer referencia a ellos en tu código como R.string.affirmation1 o R.string.affirmation2.

Cómo crear un nuevo paquete

Organiza tu código de forma lógica para que tú y otros desarrolladores lo comprendan, mantengan y extiendan. De la misma manera en que puedes organizar los papeles en archivos y carpetas, puedes organizar tu código en archivos y paquetes.

¿Qué es un paquete?

  1. En Android Studio, en la ventana Project (Android), revisa los archivos de tu nuevo proyecto en app > java para la app de Affirmations. Deberían ser similares a la captura de pantalla que se muestra a continuación, donde aparecen tres paquetes, uno para tu código (com.example.affirmations) y dos para los archivos de prueba (com.example.affirmations (androidTest) y com.example.affirmations (test)).

809a0d77a0759dc5.png

  1. Observa que el nombre del paquete consta de varias palabras separadas por un punto.

Existen dos formas de usar los paquetes.

  • Crea diferentes paquetes para diferentes partes de tu código. Por ejemplo, los desarrolladores a menudo separan las clases que trabajan con los datos y las clases que compilan la IU en diferentes paquetes.
  • Usa código de otros paquetes en tu código. Para usar las clases de otros paquetes, debes definirlas en las dependencias de tu sistema de compilación. También se recomienda usar import para importarlas a tu código a fin de que puedas usar los nombres cortos (p. ej., TextView) en lugar de los nombres completamente calificados (p. ej., android.widget.TextView). Por ejemplo, ya usaste declaraciones import para clases como sqrt (import kotlin.math.sqrt) y View (import android.view.View).

En la app de Affirmations, además de importar clases de Android y Kotlin, organizarás la app en varios paquetes. Incluso cuando no tengas muchas clases para tu app, te recomendamos usar paquetes a fin de agrupar clases por funcionalidad.

Cómo nombrar paquetes

Puedes asignar cualquier nombre, siempre que sea único a nivel global. Ningún otro paquete publicado en cualquier lugar puede tener el mismo nombre. Debido a que hay una gran cantidad de paquetes, y cuando hay nombres únicos aleatorios es difícil, los programadores usan convenciones para facilitar la creación y la comprensión de los nombres de paquetes.

  • El nombre del paquete suele estar estructurado de forma general a específica, y cada parte del nombre está en letras minúsculas y separada por un punto. Importante: El punto es solo una parte del nombre. No indica una jerarquía en el código ni la estructura de una carpeta.
  • Debido a que los dominios de Internet son únicos a nivel global, es una convención que utilices un dominio, en general, el tuyo o el de tu empresa, como primera parte del nombre.
  • Puedes elegir los nombres de los paquetes para indicar qué hay dentro y cómo se relacionan entre sí.
  • Para ejemplos de código como este, se suele usar com.example seguido del nombre de la app.

A continuación, se presentan algunos ejemplos de nombres de paquetes predefinidos y su contenido:

  • kotlin.math: Funciones y constantes matemáticas.
  • android.widget: Vistas, como TextView.

Cómo crear un paquete

  1. En Android Studio, en el panel Project, haz clic con el botón derecho en app > java > com.example.affirmations y selecciona New > Package.

39f35a81a574b7ee.png

  1. En la ventana emergente New Package, observa el prefijo del nombre del paquete sugerido. La primera parte sugerida es el nombre del paquete en el que hiciste clic con el botón derecho. Aunque los nombres de los paquetes no crean una jerarquía de paquetes, la reutilización de partes del nombre se usa para indicar una relación y una organización del contenido.
  2. En la ventana emergente, agrega model al final del nombre del paquete sugerido. A menudo, los desarrolladores usan model como el nombre del paquete para las clases que modelan (o representan) los datos.

3d392f8da53adc6f.png

  1. Presiona Intro. Se creará un paquete nuevo en el paquete com.example.affirmations (raíz). Este nuevo paquete contendrá cualquier clase relacionada con los datos definida en tu app.

Cómo crear la clase de datos de Affirmation

En esta tarea, crearás una clase llamada Affirmation. Una instancia de objeto Affirmation representa una afirmación y contiene el ID de recurso de la string con la afirmación.

  1. Haz clic con el botón derecho en el paquete com.example.affirmations.model y selecciona New > Kotlin File/Class.

d9b07905f456ab1.png

  1. En la ventana emergente, selecciona Class y, luego, ingresa Affirmation como el nombre de la clase. Esto crea un archivo nuevo llamado Affirmation.kt en el paquete model.
  2. Para hacer que Affirmation sea una clase de datos, agrega la palabra clave data antes de la definición de la clase. Esto deja un error, ya que las clases de datos deben tener al menos una propiedad definida.

Affirmation.kt

package com.example.affirmations.model

data class Affirmation {
}

Cuando creas una instancia de Affirmation, debes pasar el ID de recurso para la string de afirmación. El ID del recurso es un número entero.

  1. Agrega un parámetro de número entero val stringResourceId al constructor de la clase Affirmation. Esto elimina tu error.
package com.example.affirmations.model

data class Affirmation(val stringResourceId: Int)

Cómo crear una clase para que sea una fuente de datos

Los datos que se muestran en la app pueden provenir de distintas fuentes (p. ej., dentro del proyecto de tu app o de una fuente externa que requiere conexión a Internet para descargar datos). Como resultado, es posible que los datos no estén en el formato exacto que necesitas. El resto de la app no debería tener que preocuparse por la ubicación de los datos o el formato original. Puedes y debes ocultar esta preparación de datos en una clase Datasource separada que prepara los datos para la app.

Como la preparación de datos es una preocupación diferente, coloca la clase Datasource en un paquete data independiente.

  1. En Android Studio, en la ventana Project, haz clic con el botón derecho en app > java > com.example.affirmations y selecciona New > Package.
  2. Ingresa data como la última parte del nombre del paquete.
  3. Haz clic con el botón derecho en el paquete data y selecciona New File/Class.
  4. Ingresa Datasource como el nombre de la clase.
  5. Dentro de la clase Datasource, crea una función llamada loadAffirmations().

La función loadAffirmations() debe mostrar una lista de Affirmations. Para ello, crea una lista y complétala con una instancia Affirmation para cada string de recurso.

  1. Declara List<Affirmation> como el tipo de datos que se muestra del método loadAffirmations().
  2. En el cuerpo de loadAffirmations(), agrega una sentencia return.
  3. Después de la palabra clave return, llama a listOf<>() para crear una List.
  4. Dentro de los paréntesis angulares <>, especifica el tipo de elementos de lista como Affirmation. Si es necesario, importa com.example.affirmations.model.Affirmation.
  5. Dentro de los paréntesis, crea una Affirmation y pasa R.string.affirmation1 como el ID de recurso, como se muestra a continuación.
Affirmation(R.string.affirmation1)
  1. Agrega los objetos Affirmation restantes a la lista de todas las afirmaciones, separados por comas. El código finalizado debería verse como el siguiente.

Datasource.kt

package com.example.affirmations.data

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

class Datasource {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1),
            Affirmation(R.string.affirmation2),
            Affirmation(R.string.affirmation3),
            Affirmation(R.string.affirmation4),
            Affirmation(R.string.affirmation5),
            Affirmation(R.string.affirmation6),
            Affirmation(R.string.affirmation7),
            Affirmation(R.string.affirmation8),
            Affirmation(R.string.affirmation9),
            Affirmation(R.string.affirmation10)
        )
    }
}

[Opcional] Muestra el tamaño de la lista de Affirmations en un TextView

Para verificar que puedes crear una lista de afirmaciones, puedes llamar a loadAffirmations() y mostrar el tamaño de la lista de afirmaciones que aparecen en el TextView incluido con tu plantilla de app Empty Activity.

  1. En layouts/activity_main.xml, asigna al TextView incluido con la plantilla un id de textview.
  2. En MainActivity, en el método onCreate() después del código existente, obtén una referencia a textview.
val textView: TextView = findViewById(R.id.textview)
  1. Después, agrega el código para crear y mostrar el tamaño de la lista de afirmaciones. Crea un Datasource, llama a loadAffirmations(), obtén el tamaño de la lista que se muestra, conviértelo en una string y asígnalo como text de textView.
textView.text = Datasource().loadAffirmations().size.toString()
  1. Ejecuta la app. La pantalla debería verse como se muestra a continuación.

b4005973d4a0efc8.png

  1. Borra el código que acabas de agregar en MainActivity.

En esta tarea, configurarás un RecyclerView para mostrar la lista de Affirmations.

Hay varias piezas involucradas en la creación y el uso de RecyclerView. Imagina que son una división del trabajo. En el siguiente diagrama, se muestra una descripción general, y obtendrás más información sobre cada pieza a medida que la implementes.

  • item: Un elemento de datos de la lista para mostrar. Representa un objeto Affirmation en tu app.
  • Adapter: Toma datos y los prepara para que RecyclerView los muestre.
  • ViewHolders: Un conjunto de vistas para que RecyclerView lo use y reutilice a fin de mostrar afirmaciones.
  • RecyclerView: Vistas en la pantalla.

4e9c18b463f00bf7.png

Cómo agregar un RecyclerView al diseño

La app de Affirmations consta de una sola actividad llamada MainActivity, y su archivo de diseño se llama activity_main.xml. Primero, debes agregar el elemento RecyclerView al diseño de MainActivity.

  1. Abre activity_main.xml (app > res > layout > activity_main.xml)
  2. Si todavía no la utilizas, cambia a la opción Split view.

7e7c3e8429267dac.png

  1. Borra el TextView.

El diseño actual usa ConstraintLayout. ConstraintLayout es ideal y flexible cuando quieres posicionar varias vistas secundarias en un diseño. Como tu diseño solo tiene una sola vista secundaria, RecyclerView, puedes cambiar a un ViewGroup más simple llamado FrameLayout, que se debe usar para mantener una sola vista secundaria.

9e6f235be5fa31a8.png

  1. En el archivo XML, reemplaza ConstraintLayout por FrameLayout. El diseño completado debe verse como se muestra a continuación.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
</FrameLayout>
  1. Cambia a la vista Design.
  2. En Palette, selecciona Containers y busca RecyclerView.
  3. Arrastra una RecyclerView al diseño.
  4. Si aparece, lee la ventana emergente Add Project Dependency y haz clic en OK. (Si no aparece la ventana emergente, no se requiere ninguna acción).
  5. Espera a que Android Studio termine y que RecyclerView aparezca en tu diseño.
  6. Si es necesario, cambia los atributos layout_width y layout_height de RecyclerView a match_parent para que RecyclerView pueda llenar toda la pantalla.
  7. Configura el ID del recurso de RecyclerView como recycler_view.

RecyclerView admite mostrar elementos de diferentes formas, como una lista lineal o una cuadrícula. La organización de los elementos se controla mediante un LayoutManager. El framework de Android proporciona administradores de diseño para diseños básicos de elementos. La app de Affirmations muestra elementos como una lista vertical, por lo que puedes usar LinearLayoutManager.

  1. Vuelve a la vista Code. En el código XML, dentro del elemento RecyclerView, agrega LinearLayoutManager como atributo del administrador de diseño de RecyclerView, como se muestra a continuación.
app:layoutManager="LinearLayoutManager"

Para poder desplazarte por una lista vertical de elementos más extensa que la pantalla, debes agregar una barra de desplazamiento vertical.

  1. En RecyclerView, agrega un atributo android:scrollbars configurado como vertical.
android:scrollbars="vertical"

El diseño XML final debería verse de la siguiente manera:

activity_main.xml

<FrameLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        app:layoutManager="LinearLayoutManager" />

</FrameLayout>
  1. Ejecuta tu app.

El proyecto debería compilarse y ejecutarse sin problemas. Sin embargo, solo se muestra un fondo blanco en la app porque falta un fragmento de código crucial. Ahora, tienes la fuente de los datos y RecyclerView agregados a tu diseño, pero RecyclerView no tiene información sobre cómo mostrar los objetos Affirmation.

Cómo implementar un Adapter para RecyclerView

Tu app necesita una forma de tomar los datos de Datasource y formatearlos para que se pueda mostrar cada Affirmation como un elemento en RecyclerView.

Adapter es un patrón de diseño que adapta los datos a algo que puede usar RecyclerView. En este caso, necesitas un adaptador que tome una instancia de Affirmation de la lista que muestra loadAffirmations() y la convierta en una vista de elementos de lista para que pueda aparecer en RecyclerView..

Cuando ejecutas la app, RecyclerView usa el adaptador para descifrar cómo mostrar tus datos en la pantalla. RecyclerView le pide al adaptador que cree una nueva vista de elementos de lista para el primer elemento de datos de tu lista. Una vez que tenga la vista, le solicitará al adaptador que proporcione los datos para dibujar el elemento. Este proceso se repite hasta que RecyclerView no necesita más vistas para cubrir la pantalla. Si solo 3 vistas de elementos de la lista se ajustan a la pantalla a la vez, RecyclerView solo solicita al adaptador que prepare esas 3 vistas de elementos de lista (en lugar de las 10 vistas de elementos de lista).

En este paso, compilarás un adaptador que adaptará una instancia del objeto Affirmation para que pueda mostrarse en RecyclerView.

Cómo crear el Adapter

Un adaptador tiene varias partes, y escribirás bastante código más complejo que lo que hiciste en este curso hasta ahora. No hay problema si al principio no comprendes todos los detalles. Después de completar toda la app con un RecyclerView, podrás comprender mejor cómo encajan todas las partes. También podrás volver a usar este código como base para las futuras apps que crees con un RecyclerView.

Cómo crear un diseño para elementos

Cada elemento de RecyclerView tiene su propio diseño, que se define en un archivo de diseño separado. Dado que solo se mostrará una string, puedes usar TextView para el diseño de tu elemento.

  1. En res > layout, crea un nuevo File vacío llamado list_item.xml.
  2. Abre list_item.xml en la vista Code.
  3. Agrega un TextView con id item_title.
  4. Agrega wrap_content para layout_width y layout_height, como se muestra en el siguiente código.

Ten en cuenta que no necesitas un ViewGroup alrededor de tu diseño, ya que este diseño de elemento de lista se aumentará y se agregará como elemento secundario del elemento superior RecyclerView.

list_item.xml

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

También podrías haber usado File > New > Layout Resource File, con File name list_item.xml y TextView como Root element. Luego, actualiza el código generado para que coincida con el código anterior.

dbb34ca516c804c6.png

Cómo crear una clase ItemAdapter

  1. En Android Studio, en el panel Project, haz clic con el botón derecho en app > java > com.example.affirmations y selecciona New > Package.
  2. Ingresa adapter como la última parte del nombre del paquete.
  3. Haz clic con el botón derecho en el paquete adapter y selecciona New > Kotlin File/Class.
  4. Ingresa ItemAdapter como el nombre de la clase y termina. Se abre el archivo ItemAdapter.kt.

Debes agregar un parámetro al constructor de ItemAdapter para poder pasar la lista de afirmaciones al adaptador.

  1. Agrega un parámetro al constructor ItemAdapter, que es un val llamado dataset de tipo List<Affirmation>. Importa Affirmation si es necesario.
  2. Como el objeto dataset solo se usará dentro de esta clase, hazlo private.

ItemAdapter.kt

import com.example.affirmations.model.Affirmation

class ItemAdapter(private val dataset: List<Affirmation>) {

}

ItemAdapter necesita información sobre cómo resolver los recursos de strings. Esta y otra información sobre la app se almacena en una instancia del objeto Context, que puedes pasar a una instancia ItemAdapter.

  1. Agrega un parámetro al constructor ItemAdapter, que es un val llamado context de tipo Context. Ubícalo como el primer parámetro en el constructor.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

}

Cómo crear un ViewHolder

RecyclerView no interactúa directamente con las vistas de elementos, sino con ViewHolders. Un objeto ViewHolder representa una sola vista de elementos de lista en RecyclerView y se puede volver a usar cuando sea posible. Una instancia ViewHolder contiene referencias a las vistas individuales dentro de un diseño de elemento de lista (por eso se denomina, "view holder", "contenedor de vistas"). Esto facilita la actualización de la vista de elemento de lista con datos nuevos. Los contenedores de vistas también agregan información que RecyclerView usa para mover las vistas de manera eficiente por la pantalla.

  1. Dentro de la clase ItemAdapter, antes de la llave de cierre para ItemAdapter, crea una clase ItemViewHolder.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

    class ItemViewHolder()
}
  • Definir una clase dentro de otra clase se llama crear una clase anidada.
  • Como ItemAdapter solo usa ItemViewHolder, si se crea dentro de ItemAdapter, se muestra esta relación. Esto no es obligatorio, pero ayuda a otros desarrolladores a comprender la estructura de tu programa.
  1. Agrega un private val view de tipo View como parámetro al constructor de la clase ItemViewHolder.
  2. Haz que ItemViewHolder sea una subclase de RecyclerView. ViewHolder y pasa el parámetro view al constructor de la superclase.
  3. Dentro de ItemViewHolder, define una propiedad val de textView que sea del tipo TextView. Asigna la vista con el ID item_title que definiste en list_item.xml.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }
}

Cómo anular métodos de adaptador

  1. Agrega el código para extender tu ItemAdapter de la clase abstracta RecyclerView.Adapter. Especifica ItemAdapter.ItemViewHolder como el tipo de contenedor de vistas entre paréntesis angulares.
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }
}

Verás un error porque debes implementar algunos métodos abstractos de RecyclerView.Adapter.

  1. Coloca el cursor en ItemAdapter y presiona Command + I (Control + I en Windows). Esto te muestra la lista de métodos que debes implementar: getItemCount(), onCreateViewHolder() y onBindViewHolder().

7a8a383a8633094b.png

  1. Selecciona las tres funciones con Mayúsculas + clic y haz clic en OK.

Esto crea stubs con los parámetros correctos para los tres métodos, como se muestra a continuación.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
    TODO("Not yet implemented")
}

override fun getItemCount(): Int {
    TODO("Not yet implemented")
}

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
    TODO("Not yet implemented")
}

No deberías ver más errores. A continuación, debes implementar esos métodos a fin de que realicen las acciones correctas para tu app.

Cómo implementar getItemCount()

El método getItemCount() debe mostrar el tamaño de tu conjunto de datos. Los datos de tu app se encuentran en la propiedad dataset que pasas al constructor ItemAdapter, y puedes obtener su tamaño con size.

  1. Reemplaza getItemCount() por lo siguiente:
override fun getItemCount() = dataset.size

Es una forma más concisa de escribir lo siguiente:

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

Cómo implementar onCreateViewHolder()

El administrador de diseño llama al método onCreateViewHolder() a fin de crear nuevas interfaces de vista para RecyclerView (cuando no hay contenedores de vistas existentes que puedan reutilizarse). Recuerda que un contenedor de vistas representa una sola vista de elementos de lista.

El método onCreateViewHolder() toma dos parámetros y muestra un ViewHolder nuevo.

  • Un parámetro parent, que es el grupo de vistas al que se adjuntará la nueva vista de elemento de la lista como elemento secundario. El superior es el RecyclerView.
  • Un parámetro viewType, que se vuelve importante cuando hay varios tipos de vistas de elementos en el mismo RecyclerView. Si tienes diferentes diseños de elementos de lista en RecyclerView, existen diferentes tipos de vistas de elementos. Solo puedes reciclar vistas con el mismo tipo de vista de elemento. En tu caso, solo hay un diseño de elemento de lista y un tipo de vista de elemento, por lo que no tienes que preocuparte por este parámetro.
  1. En el método onCreateViewHolder(), obtén una instancia de LayoutInflater a partir del contexto proporcionado (context de parent). El amplificador de diseño sabe cómo aumentar un diseño XML a una jerarquía de objetos de vista.
val adapterLayout = LayoutInflater.from(parent.context)
  1. Cuando tengas una instancia de objeto LayoutInflater, agrega un punto seguido de otra llamada de método para aumentar la vista real de elementos de lista. Pasa el ID de recurso de diseño XML R.layout.list_item y el grupo de vistas parent. El tercer argumento booleano es attachToRoot. Este argumento debe ser false, porque RecyclerView agrega este elemento a la jerarquía de vistas cuando sea el momento.
val adapterLayout = LayoutInflater.from(parent.context)
       .inflate(R.layout.list_item, parent, false)

Ahora adapterLayout tiene una referencia a la vista de elementos de lista (donde más adelante podemos encontrar

vistas secundarias como TextView).

  1. En onCreateViewHolder(), muestra una nueva instancia de ItemViewHolder en la que la vista raíz es adapterLayout.
return ItemViewHolder(adapterLayout)

Este es el código para onCreateViewHolder() hasta ahora.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
    // create a new view
    val adapterLayout = LayoutInflater.from(parent.context)
        .inflate(R.layout.list_item, parent, false)

    return ItemViewHolder(adapterLayout)
}

Cómo implementar onBindViewHolder()

El último método que debes anular es onBindViewHolder(). El administrador de diseño llama a este método para reemplazar el contenido de una vista de elementos de lista.

El método onBindViewHolder() tiene dos parámetros: ItemViewHolder, creado anteriormente por el método onCreateViewHolder(), y un int que representa el elemento actual position en la lista. En este método, encontrarás el objeto Affirmation adecuado del conjunto de datos según la posición.

  1. Dentro de onBindViewHolder(), crea un val item y obtén el elemento en la position determinada en el dataset.
val item = dataset[position]

Por último, debes actualizar todas las vistas a las que hace referencia el contenedor de vistas para reflejar los datos correctos de este elemento. En este caso, solo hay una vista: TextView dentro de ItemViewHolder. Configura el texto de TextView para mostrar la string Affirmation de este elemento.

  1. Con una instancia de objeto Affirmation, puedes buscar el ID de recurso de strings correspondiente llamando a item.stringResourceId. Sin embargo, es un número entero y necesitas encontrar la asignación al valor de string real.

En el framework de Android, puedes llamar a getString() con un ID de recurso de strings, y este mostrará el valor de string asociado. getString() es un método en la clase Resources, y puedes obtener una instancia de la clase Resources a través de context.

Esto significa que puedes llamar a context.resources.getString() y pasar un ID de recurso de strings. La string resultante se puede establecer como text de textView en holder ItemViewHolder. En resumen, esta línea de código actualiza el contenedor de vistas para mostrar la string de afirmación.

holder.textView.text = context.resources.getString(item.stringResourceId)

El método onBindViewHolder() completo debería verse de la siguiente manera:

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
    val item = dataset[position]
    holder.textView.text =  context.resources.getString(item.stringResourceId)
}

Aquí está el código de adaptador terminado.

ItemAdapter.kt

package com.example.affirmations.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

/**
 * Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
 */
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    // 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 an Affirmation object.
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }

    /**
     * Create new views (invoked by the layout manager)
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout)
    }

    /**
     * Replace the contents of a view (invoked by the layout manager)
     */
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text = context.resources.getString(item.stringResourceId)
    }

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

Ahora que implementaste el ItemAdapter, debes indicarle a RecyclerView que use este adaptador.

Cómo modificar MainActivity para usar un RecyclerView

Para finalizar, debes usar tus clases Datasource y ItemAdapter a fin de crear y mostrar elementos en RecyclerView. Puedes hacerlo en MainActivity.

  1. Abre MainActivity.kt.
  2. En MainActivity,, ve al método onCreate(). Inserta el nuevo código descrito en los siguientes pasos después de la llamada a setContentView(R.layout.activity_main)..
  3. Crea una instancia de Datasource y llama al método loadAffirmations(). Almacena la lista de afirmaciones que se muestra en una val llamada myDataset.
val myDataset = Datasource().loadAffirmations()
  1. Crea una variable llamada recyclerView y usa findViewById() para buscar una referencia al RecyclerView dentro del diseño.
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
  1. Para indicarle a recyclerView que use la clase ItemAdapter que creaste, crea una instancia ItemAdapter nueva. ItemAdapter espera dos parámetros: el contexto (this) de esta actividad y las afirmaciones en myDataset.
  2. Asigna el objeto ItemAdapter a la propiedad adapter de recyclerView.
recyclerView.adapter = ItemAdapter(this, myDataset)
  1. Como el tamaño del diseño de tu RecyclerView es fijo en el diseño de la actividad, puedes establecer el parámetro setHasFixedSize de RecyclerView en true. Esta configuración solo es necesaria para mejorar el rendimiento. Úsala si sabes que los cambios en el contenido no cambiarán el tamaño del diseño de RecyclerView.
recyclerView.setHasFixedSize(true)
  1. Cuando termines, el código para MainActivity debe ser similar al siguiente.

MainActivity.kt

package com.example.affirmations

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource

class MainActivity : AppCompatActivity() {

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

        // Initialize data.
        val myDataset = Datasource().loadAffirmations()

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = ItemAdapter(this, myDataset)

        // 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)
    }
}
  1. Ejecuta la app. Deberías ver una lista de strings de afirmación en la pantalla.

427c10d4f29d769d.png

¡Felicitaciones! Acabas de crear una app que muestra una lista de datos con RecyclerView y un adaptador personalizado. Dedica un tiempo a analizar el código que creaste y comprender cómo funcionan en conjunto las distintas piezas.

Esta app cuenta con todas las piezas necesarias para mostrar tus afirmaciones, pero aún no está lista para la etapa de producción. Se podría mejorar la IU. En el siguiente codelab, mejorarás tu código, aprenderás a agregar imágenes a la app y perfeccionarás la IU.

El código de la solución para este codelab se encuentra en el proyecto y módulo que se muestran a continuación. Ten en cuenta que algunos de los archivos Kotlin están en paquetes diferentes, como lo indica la sentencia package al comienzo del archivo.

res/values/strings.xml

<resources>
    <string name="app_name">Affirmations</string>
    <string name="affirmation1">I am strong.</string>
    <string name="affirmation2">I believe in myself.</string>
    <string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
    <string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
    <string name="affirmation5">I have so much to be grateful for.</string>
    <string name="affirmation6">Good things are always coming into my life.</string>
    <string name="affirmation7">New opportunities await me at every turn.</string>
    <string name="affirmation8">I have the courage to follow my heart.</string>
    <string name="affirmation9">Things will unfold at precisely the right time.</string>
    <string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>

affirmations/data/Datasource.kt

package com.example.affirmations.data

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

class Datasource {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1),
            Affirmation(R.string.affirmation2),
            Affirmation(R.string.affirmation3),
            Affirmation(R.string.affirmation4),
            Affirmation(R.string.affirmation5),
            Affirmation(R.string.affirmation6),
            Affirmation(R.string.affirmation7),
            Affirmation(R.string.affirmation8),
            Affirmation(R.string.affirmation9),
            Affirmation(R.string.affirmation10)
        )
    }
}

affirmations/model/Affirmation.kt

package com.example.affirmations.model

data class Affirmation(val stringResourceId: Int)

affirmations/MainActivty.kt

package com.example.affirmations

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource

class MainActivity : AppCompatActivity() {

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

        // Initialize data.
        val myDataset = Datasource().loadAffirmations()

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = ItemAdapter(this, myDataset)

        // 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)
    }
}

affirmations/adapter/ItemAdapter.kt

package com.example.affirmations.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

/**
 * Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
 */
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    // 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 an Affirmation object.
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }

    /**
     * Create new views (invoked by the layout manager)
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout)
    }

    /**
     * Replace the contents of a view (invoked by the layout manager)
     */
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text = context.resources.getString(item.stringResourceId)
    }

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

src/main/res/layout/activty_main.xml

<FrameLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        app:layoutManager="LinearLayoutManager" />

</FrameLayout>

src/main/res/layout/list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
  • El widget de RecyclerView te ayuda a mostrar una lista de datos.
  • RecyclerView usa el patrón del adaptador para adaptar y mostrar los datos.
  • ViewHolder crea y contiene las vistas para RecyclerView.
  • RecyclerView incluye LayoutManagers. RecyclerView delega la distribución de los elementos a LayoutManagers.

Para implementar el adaptador, sigue estos pasos:

  • Crea una nueva clase para el adaptador, por ejemplo, ItemAdapter.
  • Crea una clase ViewHolder personalizada que represente una sola vista de elementos de lista. Realiza la extensión desde la clase RecyclerView.ViewHolder.
  • Modifica la clase ItemAdapter para que se extienda desde la clase RecyclerView.Adapter con la clase personalizada ViewHolder.
  • Implementa estos métodos dentro del adaptador: getItemsCount(), onCreateViewHolder() y onBindViewHolder().