Aspectos básicos de Kotlin para Android 07.1: Aspectos básicos de RecyclerView

Este codelab es parte del curso Conceptos básicos de Kotlin para Android. Aprovecharás al máximo este curso si trabajas con los codelabs en secuencia. Todos los codelabs del curso se detallan en la página de destino de codelabs sobre los aspectos básicos de Kotlin para Android.

Introducción

En este codelab, aprenderás a usar un RecyclerView para mostrar listas de elementos. A partir de la app de seguimiento de sueño de la serie anterior de codelabs, aprenderás una forma mejor y más versátil de mostrar datos, con un elemento RecyclerView con una arquitectura recomendada.

Conocimientos que ya deberías tener

Debes estar familiarizado con lo siguiente:

  • Compilar una interfaz de usuario básica (IU) mediante una actividad, fragmentos y vistas
  • Navegar entre fragmentos y usar safeArgs para pasar datos entre fragmentos
  • Usar modelos de vista, fábricas de modelos de vista, transformaciones y LiveData y sus observadores
  • Crear una base de datos Room, crear un DAO y definir entidades
  • Cómo usar corrutinas para tareas de bases de datos y otras tareas de larga duración

Qué aprenderás

  • Cómo usar un RecyclerView con una Adapter y un ViewHolder a los efectos de mostrar una lista de elementos

Actividades

  • Cambia la app TrackMySleepQuality de la lección anterior para usar un elemento RecyclerView a fin de mostrar datos de calidad del sueño.

En este codelab, compilarás la parte de RecyclerView de una app que realice un seguimiento de la calidad del sueño. La app usa una base de datos Room para almacenar datos de sueño a lo largo del tiempo.

La app de monitor de sueño inicial tiene dos pantallas, representadas por fragmentos, como se muestra en la siguiente figura.

La primera pantalla, que se muestra a la izquierda, tiene botones para iniciar y detener el seguimiento. En esta pantalla, también se muestran todos los datos de sueño del usuario. El botón Borrar borra de forma permanente todos los datos que la app recopiló para el usuario. En la segunda pantalla, que se muestra a la derecha, se selecciona una calificación de calidad del sueño.

Esta app usa una arquitectura simplificada con un controlador de IU, ViewModel y LiveData. La app también usa una base de datos Room para que los datos de sueño sean persistentes.

La lista de las noches de sueño que se muestran en la primera pantalla es funcional, pero no bonita. La app usa un formateador complejo a fin de crear strings de texto para la vista de texto y números de calidad. Además, este diseño no se escala. Una vez que corrijas todos estos problemas en este codelab, la app final tendrá la misma funcionalidad y la pantalla principal se verá de la siguiente manera:

Mostrar una lista o cuadrícula de datos es una de las tareas de IU más comunes de Android. Las listas varían de simples a muy complejas. Es posible que una lista de vistas de texto muestre datos simples, como una lista de compras. Una lista compleja, como una lista anotada de destinos de vacaciones, puede mostrarle al usuario muchos detalles dentro de una cuadrícula desplazable con encabezados.

Para admitir todos estos casos de uso, Android proporciona el widget RecyclerView.

El mayor beneficio de RecyclerView es que es muy eficiente para las listas grandes:

  • De forma predeterminada, RecyclerView solo trabaja para procesar o dibujar elementos que estén visibles actualmente en la pantalla. Por ejemplo, si tu lista tiene mil elementos, pero solo 10 son visibles, RecyclerView solo hace el trabajo necesario para dibujar 10 elementos en la pantalla. Cuando el usuario se desplaza, RecyclerView detecta los elementos nuevos que deben aparecer en la pantalla y hace el trabajo necesario para mostrar solo esos elementos.
  • Cuando un elemento se desplaza fuera de la pantalla, se reciclan las vistas del elemento. Eso significa que el elemento se rellena con contenido nuevo que se desplaza hacia la pantalla. Este comportamiento de RecyclerView permite ahorrar mucho tiempo de procesamiento y permite que las listas se desplacen sin problemas.
  • Cuando cambia un elemento, en lugar de volver a dibujar la lista completa, RecyclerView puede actualizar ese elemento. Esto representa una gran ventaja en la eficiencia cuando se muestran listas de elementos complejos.

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.

El patrón del adaptador

Si alguna vez viajas por países que usan diferentes enchufes eléctricos, es probable que sepas cómo conectar tus dispositivos a enchufes con un adaptador. El adaptador te permite convertir un tipo de conector en otro, lo que realmente convierte una interfaz en otra.

El patrón de adaptador en la ingeniería de software ayuda a un objeto a trabajar con otra API. RecyclerView usa un adaptador para transformar los datos de la app en algo que puede mostrar el RecyclerView, sin cambiar la manera en que la app almacena y procesa los datos. Para la app de seguimiento de sueño, compilas un adaptador que adapta los datos de la base de datos de Room a algo que RecyclerView sabe cómo mostrar, sin cambiar el ViewModel.

Cómo implementar una RecyclerView

Para mostrar tus datos en un RecyclerView, necesitas las siguientes partes:

  • Datos para mostrar
  • Una instancia de RecyclerView definida en tu archivo de diseño para que actúe como contenedor de las vistas.
  • Un diseño para un elemento de datos.
    Si todos los elementos de la lista son iguales, puedes usar el mismo diseño para todos ellos, pero no es obligatorio. El diseño del elemento debe crearse por separado del diseño del fragmento, de modo que se pueda crear una vista de un elemento a la vez y completarla con datos.
  • Un administrador de diseño.
    El administrador de diseño se encarga de la organización (el diseño) de los componentes de la IU en una vista.
  • Un contenedor de vistas.
    El contenedor amplía la clase ViewHolder. Contiene la información de la vista para mostrar un elemento del diseño. Los contenedores de vistas también agregan información que RecyclerView usa para mover las vistas de manera eficiente por la pantalla.
  • Un adaptador.
    El adaptador conecta tus datos a RecyclerView. Adapta los datos para que se puedan mostrar en un elemento ViewHolder. Un objeto RecyclerView usa el adaptador para determinar cómo mostrar los datos en la pantalla.

En esta tarea, agregarás una RecyclerView a tu archivo de diseño y configurarás un Adapter a fin de exponer los datos de sueño a RecyclerView.

Paso 1: Agrega RecyclerView con LayoutManager

En este paso, reemplazas ScrollView por RecyclerView en el archivo fragment_sleep_tracker.xml.

  1. Descarga la app de RecyclerViewFundamentals-Starter desde GitHub.
  2. Compila y ejecuta la app. Observa cómo los datos se muestran como texto simple.
  3. Abre el archivo de diseño fragment_sleep_tracker.xml en la pestaña Design de Android Studio.
  4. En el panel Component Tree, borra ScrollView. Esta acción también borra los TextView dentro del ScrollView.
  5. En el panel Palette, desplázate por la lista de tipos de componentes de la izquierda para encontrar Containers y, luego, selecciónalo.
  6. Arrastra un elemento RecyclerView del panel Palette al panel Component Tree. Coloca el RecyclerView dentro del ConstraintLayout.

  1. Si se abre un diálogo en el que se te pregunta si deseas agregar una dependencia, haz clic en OK para permitir que Android Studio agregue la dependencia recyclerview a tu archivo Gradle. Es posible que la app tarde unos segundos en sincronizarse con la app.

  1. Abre el archivo del módulo build.gradle, desplázate hasta el final y toma nota de la nueva dependencia, que es similar al siguiente código:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. Vuelve a fragment_sleep_tracker.xml.
  2. En la pestaña Text, busca el código RecyclerView que se muestra a continuación:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Dale a RecyclerView un id de sleep_list.
android:id="@+id/sleep_list"
  1. Coloca el objeto RecyclerView de modo que ocupe la parte restante de la pantalla dentro del ConstraintLayout. Para ello, restringe la parte superior del RecyclerView al botón Start, luego al botón Clear, y al lado del padre a cada lado. Establece el ancho y el alto del diseño en 0 dp en el editor de diseño o en XML mediante el siguiente código:
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toTopOf="@+id/clear_button"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/stop_button"
  1. Agrega un administrador de diseño al archivo XML RecyclerView. Cada RecyclerView necesita un administrador de diseño que le indique cómo posicionar los elementos en la lista. Android proporciona un LinearLayoutManager que, de forma predeterminada, organiza los elementos en una lista vertical de filas de ancho completo.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. Cambia a la pestaña Design y observa que las restricciones agregadas hicieron que RecyclerView se expanda para llenar el espacio disponible.

Paso 2: Crea el diseño del elemento de la lista y el contenedor de vistas de texto

El RecyclerView es solo un contenedor. En este paso, crearás el diseño y la infraestructura para los elementos que se mostrarán dentro de RecyclerView.

Para acceder a un elemento RecyclerView que funcione lo más rápido posible, al principio usa un elemento de lista simple que solo muestra la calidad del sueño como un número. Para ello, necesitas un contenedor de vistas, TextItemViewHolder. También necesitas una vista, un TextView, para los datos. En un paso posterior, obtendrás más información sobre los contenedores de vistas y cómo distribuir todos los datos de sueño.

  1. Crea un archivo de diseño llamado text_item_view.xml. No importa qué uses como elemento raíz, ya que reemplazarás el código de la plantilla.
  2. En text_item_view.xml, borra todo el código dado.
  3. Agrega un TextView con relleno 16dp al principio y al final, y un tamaño de texto de 24sp. Permite que el ancho coincida con el del elemento superior, y la altura se une al contenido. Como esta vista se muestra dentro de la RecyclerView, no es necesario que la coloques dentro de un ViewGroup.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:textSize="24sp"
    android:paddingStart="16dp"
    android:paddingEnd="16dp"
    android:layout_width="match_parent"       
    android:layout_height="wrap_content" />
  1. Abre Util.kt. Desplázate hasta el final y agrega la definición que se muestra a continuación, lo que creará la clase TextItemViewHolder. Coloca el código en la parte inferior del archivo, después de la última llave de cierre. El código se incluirá en Util.kt porque este contenedor de vistas es temporal y lo reemplazarás más adelante.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. Si se te solicita, importa android.widget.TextView y androidx.recyclerview.widget.RecyclerView.

Paso 3: Crea SleepNightAdapter

La tarea principal para implementar un RecyclerView es crear el adaptador. Tienes un contenedor de vistas simple para la vista de elementos y un diseño para cada elemento. Ahora puedes crear un adaptador. El adaptador crea un contenedor de vistas y lo llena con datos para que RecyclerView se muestre.

  1. En el paquete sleeptracker, crea una nueva clase de Kotlin llamada SleepNightAdapter.
  2. Haz que la clase SleepNightAdapter extienda RecyclerView.Adapter. La clase se llama SleepNightAdapter porque adapta un objeto SleepNight a algo que puede usar RecyclerView. El adaptador necesita saber qué contenedor de vistas usar, por lo que debes pasar TextItemViewHolder. Importa los componentes necesarios cuando se te solicite y verás un error, ya que existen métodos obligatorios para implementar.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. En el nivel superior de SleepNightAdapter, crea una variable listOf SleepNight para conservar los datos.
var data =  listOf<SleepNight>()
  1. En SleepNightAdapter, anula getItemCount() para mostrar el tamaño de la lista de noches de sueño en data. RecyclerView necesita saber cuántos elementos tiene el adaptador para mostrar y lo hace llamando a getItemCount().
override fun getItemCount() = data.size
  1. En SleepNightAdapter, anula la función onBindViewHolder(), como se muestra a continuación.

    RecyclerView llama a la función onBindViewHolder() a fin de mostrar los datos para un elemento de la lista en la posición especificada. Por lo tanto, el método onBindViewHolder() toma dos argumentos: un contenedor de vistas y una posición de los datos para vincular. Para esta app, el titular es el TextItemViewHolder y la posición es la posición en la lista.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. Dentro de onBindViewHolder(), crea una variable para un elemento en una posición determinada de los datos.
 val item = data[position]
  1. La ViewHolder que creaste tiene una propiedad llamada textView. Dentro de onBindViewHolder(), establece el text de textView en el número de calidad del sueño. Este código solo muestra una lista de números, pero este ejemplo simple te permite ver cómo el adaptador ingresa los datos en el contenedor de vistas y en la pantalla.
holder.textView.text = item.sleepQuality.toString()
  1. En SleepNightAdapter, anula e implementa onCreateViewHolder(), que se llama cuando RecyclerView necesita un contenedor de vistas para representar un elemento.

    Esta función toma dos parámetros y muestra un ViewHolder. El parámetro parent, que es el grupo de vistas que contiene el contenedor de vistas, siempre es el RecyclerView. El parámetro viewType se usa cuando hay varias vistas en el mismo RecyclerView. Por ejemplo, si incluyes una lista de vistas de texto, una imagen y un video todos en el mismo RecyclerView, la función onCreateViewHolder() necesita saber qué tipo de vista usar.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. En onCreateViewHolder(), crea una instancia de LayoutInflater.

    El amplificador de diseño sabe cómo crear vistas a partir de diseños XML. context contiene información sobre cómo aumentar la vista correctamente. En un adaptador para una vista de reciclador, siempre pasas en el contexto del grupo de vistas parent, que es RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. En onCreateViewHolder(), crea el view pidiéndole a la layoutinflater que la aumente.

    Pasa el diseño XML en la vista 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 view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. En onCreateViewHolder(), muestra un TextItemViewHolder creado con view.
return TextItemViewHolder(view)
  1. El adaptador debe informar a RecyclerView cuándo cambió el data, ya que el elemento RecyclerView no sabe nada sobre los datos. Solo conoce los contenedores de vistas que le proporciona el adaptador.

    Para indicarle a RecyclerView cuándo cambiaron los datos que muestra, agrega un método set personalizado a la variable data que se encuentra en la parte superior de la clase SleepNightAdapter. En el método set, asigna un valor nuevo a data y, luego, llama a notifyDataSetChanged() para activar el rediseño de la lista con los datos nuevos.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

Paso 4: Informa a RecyclerView sobre el adaptador

El elemento RecyclerView debe conocer el adaptador que se usará para obtener los contenedores de vistas.

  1. Abre SleepTrackerFragment.kt.
  2. En onCreateview(), crea un adaptador. Coloca este código después de la creación del modelo ViewModel y antes de la sentencia return.
val adapter = SleepNightAdapter()
  1. Asocia el adapter con el RecyclerView.
binding.sleepList.adapter = adapter
  1. Limpia y vuelve a compilar tu proyecto para actualizar el objeto binding.

    Si aún ves errores relacionados con binding.sleepList o binding.FragmentSleepTrackerBinding, invalida las cachés y reinicia el dispositivo. (Selecciona File > Invalidate Caches / Restart).

    Si ejecutas la app ahora, no hay errores, pero no verás ningún dato cuando presiones Start y, luego, Stop.

Paso 5: Ingresa los datos en el adaptador

Hasta ahora, tienes un adaptador y una manera de obtener datos del adaptador en el RecyclerView. Ahora, debes ingresar datos en el adaptador de ViewModel.

  1. Abre SleepTrackerViewModel.
  2. Busca la variable nights, que almacena todas las noches de sueño, es decir, los datos que se muestran. La variable nights se configura llamando a getAllNights() en la base de datos.
  3. Quita private de nights, ya que crearás un observador que necesita acceder a esta variable. La declaración debería verse de la siguiente manera:
val nights = database.getAllNights()
  1. En el paquete database, abre el objeto SleepDatabaseDao.
  2. Busca la función getAllNights(). Ten en cuenta que esta función muestra una lista de valores SleepNight como LiveData. Esto significa que la variable nights contiene LiveData y Room se mantiene actualizada. Además, puedes observar nights para saber cuándo cambia.
  3. Abre SleepTrackerFragment.
  4. En onCreateView(), debajo de la creación de adapter, crea un observador en la variable nights.

    Si se proporciona el fragmento viewLifecycleOwner como propietario del ciclo de vida, puedes asegurarte de que este observador solo esté activo cuando RecyclerView esté en la pantalla.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. Dentro del observador, cuando obtengas un valor no nulo (para nights), asigna el valor al data del adaptador. Este es el código completo para el observador y la configuración de los datos:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. Compila y ejecuta tu código.

    Si tu adaptador funciona, verás los números de la calidad del sueño en una lista. La captura de pantalla de la izquierda muestra -1 después de que presionas Iniciar. En la captura de pantalla de la derecha, se muestra el número de calidad de sueño actualizado después de que presionas Detener y seleccionas una calificación de calidad.

Paso 6: Explora cómo se reciclan los contenedores de vistas

RecyclerView recicla los contenedores de vistas, lo que significa que los reutiliza. A medida que una vista se desplaza fuera de la pantalla, RecyclerView reutiliza la vista de la vista que se va a desplazar por la pantalla.

Dado que estos contenedores de vistas se reciclan, asegúrate de que onBindViewHolder() establezca o restablezca las personalizaciones que los elementos anteriores hayan establecido en un contenedor de vistas.

Por ejemplo, puede configurar el color del texto en rojo para los contenedores de vistas que tienen calificaciones de calidad inferiores o iguales a 1 y que representan un sueño deficiente.

  1. En la clase SleepNightAdapter, agrega el siguiente código al final de onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. Ejecuta la app.
  2. Si tiene datos de calidad de sueño bajos, el número será de color rojo.
  3. Agrega calificaciones altas para la calidad de sueño hasta que veas un número alto en rojo en la pantalla.

    A medida que RecyclerView reutiliza los contenedores de vistas, finalmente reutiliza uno de ellos para obtener una calificación de alta calidad. La calificación alta se muestra erróneamente en rojo.

  1. Para corregir este problema, agrega una sentencia else a fin de establecer el color en negro si la calidad no es menor o igual que una.

    En ambas condiciones, el contenedor de vistas usará el color de texto correcto para cada elemento.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
} else {
   // reset
   holder.textView.setTextColor(Color.BLACK) // black
}
  1. Ejecuta la app, y los números siempre deben tener el color correcto.

¡Felicitaciones! Ahora tienes un RecyclerView básico completamente funcional.

En esta tarea, reemplazarás el contenedor de vistas simple por uno que puede mostrar más datos para una noche de sueño.

El ViewHolder simple que agregaste a Util.kt une una TextView en un TextItemViewHolder.

class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

Entonces, ¿por qué RecyclerView no usa directamente un TextView? Esta línea de código proporciona muchas funcionalidades. Un ViewHolder describe una vista de elementos y metadatos sobre su lugar dentro de RecyclerView. RecyclerView se basa en esta funcionalidad para posicionar correctamente la vista a medida que se desplaza la lista y para realizar acciones interesantes, como animar vistas cuando se agregan o quitan elementos en Adapter.

Si RecyclerView necesita acceder a las vistas almacenadas en ViewHolder, puede hacerlo mediante la propiedad itemView del contenedor de vistas. RecyclerView usa itemView cuando vincula un elemento para que se muestre en la pantalla, cuando dibuja decoraciones alrededor de una vista como un borde y para implementar la accesibilidad.

Paso 1: Crea el diseño del elemento

En este paso, crearás el archivo de diseño para un elemento. El diseño consta de un ConstraintLayout con una ImageView para la calidad de sueño, un TextView para la duración de la suspensión y un TextView para la calidad del texto. Como ya hiciste diseños, copia y pega el código XML proporcionado.

  1. Crea un nuevo archivo de recursos de diseño y asígnale el nombre list_item_sleep_night.
  2. Reemplaza todo el código del archivo con el siguiente: Luego, familiarízate con el diseño que acabas de crear.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">

   <ImageView
       android:id="@+id/quality_image"
       android:layout_width="@dimen/icon_size"
       android:layout_height="60dp"
       android:layout_marginStart="16dp"
       android:layout_marginTop="8dp"
       android:layout_marginBottom="8dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       tools:srcCompat="@drawable/ic_sleep_5" />

   <TextView
       android:id="@+id/sleep_length"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginStart="8dp"
       android:layout_marginTop="8dp"
       android:layout_marginEnd="16dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toEndOf="@+id/quality_image"
       app:layout_constraintTop_toTopOf="@+id/quality_image"
       tools:text="Wednesday" />

   <TextView
       android:id="@+id/quality_string"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginTop="8dp"
       app:layout_constraintEnd_toEndOf="@+id/sleep_length"
       app:layout_constraintHorizontal_bias="0.0"
       app:layout_constraintStart_toStartOf="@+id/sleep_length"
       app:layout_constraintTop_toBottomOf="@+id/sleep_length"
       tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
  1. Ve a la pestaña Design en Android Studio. En la vista de diseño, tu diseño se ve como la captura de pantalla de la izquierda. En la vista de plano técnico, se ve como la captura de pantalla de la derecha.

Paso 2: Crea ViewHolder

  1. Abre SleepNightAdapter.kt.
  2. Crea una clase dentro del SleepNightAdapter llamada ViewHolder y haz que extienda RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. Dentro de ViewHolder, obtén referencias a las vistas. Necesitas una referencia a las vistas que actualizará este ViewHolder. Cada vez que vinculas este ViewHolder, debes acceder a la imagen y a ambas vistas de texto. (Conviertes este código para usar la vinculación de datos más adelante).
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)

Paso 3: Usa el ViewHolder en SleepNightAdapter

  1. En la definición de SleepNightAdapter, en lugar de TextItemViewHolder, usa el SleepNightAdapter.ViewHolder que acabas de crear.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

Actualiza onCreateViewHolder() con este comando:

  1. Cambia la firma de onCreateViewHolder() para mostrar el ViewHolder.
  2. Cambia el amplificador de diseño para usar el recurso de diseño correcto, list_item_sleep_night.
  3. Quita la transmisión a TextView.
  4. En lugar de mostrar un TextItemViewHolder, muestra un ViewHolder.

    Aquí se muestra la función onCreateViewHolder() actualizada:
    override fun onCreateViewHolder(
            parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutInflater = 
            LayoutInflater.from(parent.context)
        val view = layoutInflater
                .inflate(R.layout.list_item_sleep_night, 
                         parent, false)
        return ViewHolder(view)
    }

Actualiza onBindViewHolder() con este comando:

  1. Cambia la firma de onBindViewHolder() para que el parámetro holder sea ViewHolder en lugar de TextItemViewHolder.
  2. Dentro de onBindViewHolder(), borra todo el código, excepto la definición de item.
  3. Define un elemento val res que contenga una referencia al resources para esta vista.
val res = holder.itemView.context.resources
  1. Configura la duración del texto de la vista de texto sleepLength. Copia el siguiente código, que llama a una función de formato que se proporciona con el código de inicio.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. Se produce un error, porque se debe definir convertDurationToFormatted(). Abre Util.kt y quita los comentarios del código y de las importaciones asociadas. (Selecciona Code > Comment with Line comments).
  2. En onBindViewHolder(), usa convertNumericQualityToString() para establecer la calidad.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. Es posible que debas importar manualmente estas funciones.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. Configura el ícono de calidad correcto. El nuevo ícono ic_sleep_active se proporciona en el código de inicio.
holder.qualityImage.setImageResource(when (item.sleepQuality) {
   0 -> R.drawable.ic_sleep_0
   1 -> R.drawable.ic_sleep_1
   2 -> R.drawable.ic_sleep_2
   3 -> R.drawable.ic_sleep_3
   4 -> R.drawable.ic_sleep_4
   5 -> R.drawable.ic_sleep_5
   else -> R.drawable.ic_sleep_active
})
  1. A continuación, se muestra la función onBindViewHolder() actualizada y terminada, que configura todos los datos de ViewHolder:
   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = data[position]
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. Ejecuta la app. La pantalla debería verse como en la siguiente captura de pantalla, que muestra el ícono de calidad de sueño junto con el texto de la duración y la calidad del sueño.

Tu RecyclerView ya está completo. Aprendiste a implementar un Adapter y un ViewHolder, y los creaste para mostrar una lista con un RecyclerView Adapter.

Hasta ahora, tu código muestra el proceso para crear un adaptador y un contenedor de vistas. Sin embargo, puedes mejorar este código. El código que se mostrará y el código para administrar los contenedores de vistas están combinados, y onBindViewHolder() conoce los detalles sobre cómo actualizar el elemento ViewHolder.

En una app de producción, es posible que tengas varios contenedores de vistas, adaptadores más complejos y varios desarrolladores que realicen cambios. Debes estructurar tu código para que todo lo relacionado con un contenedor de vistas esté solo en él.

Paso 1: Refactoriza onBindViewHolder()

En este paso, refactorizas el código y mueves todas las funciones del contenedor de vistas a ViewHolder. El objetivo de esta refactorización no es cambiar cómo se ve la app para el usuario, sino facilitar y trabajar de forma más segura en el código para desarrolladores. Afortunadamente, Android Studio tiene herramientas que pueden ayudarte.

  1. En SleepNightAdapter, en onBindViewHolder(), selecciona todo (excepto la declaración) para declarar la variable item.
  2. Haz clic con el botón derecho y, luego, selecciona Refactor > Extract > Function.
  3. Asigna el nombre bind a la función y acepta los parámetros sugeridos. Haz clic en OK.

    La función bind() se ubica debajo de onBindViewHolder().
    private fun bind(holder: ViewHolder, item: SleepNight) {
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. Coloca el cursor en la palabra holder del parámetro holder de bind(). Presiona Alt+Enter (Option+Enter en Mac) para abrir el menú de intents. Selecciona Convertir el parámetro en receptor para convertir esto en una función de extensión que tenga la siguiente firma:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. Corta y pega la función bind() en ViewHolder.
  2. Hacer bind() públicas.
  3. Importa bind() al adaptador, si es necesario.
  4. Como ahora está en ViewHolder, puedes quitar la parte ViewHolder de la firma. Este es el código final de la función bind() en la clase ViewHolder.
fun bind(item: SleepNight) {
   val res = itemView.context.resources
   sleepLength.text = convertDurationToFormatted(
           item.startTimeMilli, item.endTimeMilli, res)
   quality.text = convertNumericQualityToString(
           item.sleepQuality, res)
   qualityImage.setImageResource(when (item.sleepQuality) {
       0 -> R.drawable.ic_sleep_0
       1 -> R.drawable.ic_sleep_1
       2 -> R.drawable.ic_sleep_2
       3 -> R.drawable.ic_sleep_3
       4 -> R.drawable.ic_sleep_4
       5 -> R.drawable.ic_sleep_5
       else -> R.drawable.ic_sleep_active
   })
}

Paso 2: Refactoriza onCreateViewHolder

El método onCreateViewHolder() del adaptador aumenta la vista del recurso de diseño de ViewHolder. Sin embargo, el aumento no tiene ninguna relación con el adaptador, ni con la ViewHolder. El aumento debe ocurrir en el ViewHolder.

  1. En onCreateViewHolder(), selecciona todo el código del cuerpo de la función.
  2. Haz clic con el botón derecho y, luego, selecciona Refactor > Extract > Function.
  3. Asigna el nombre from a la función y acepta los parámetros sugeridos. Haz clic en OK.
  4. Coloca el cursor sobre el nombre de la función from. Presiona Alt+Enter (Option+Enter en Mac) para abrir el menú de intents.
  5. Seleccione Mover al objeto complementario. La función from() debe estar en un objeto complementario para que se pueda llamar en la clase ViewHolder y no en una instancia de ViewHolder.
  6. Mueve el objeto companion a la clase ViewHolder.
  7. Hacer from() públicas.
  8. En onCreateViewHolder(), cambia la sentencia return a fin de mostrar el resultado de llamar a from() en la clase ViewHolder.

    Los métodos onCreateViewHolder() y from() completados deberían verse como el siguiente código, que se compilará y ejecutará sin errores.
    override fun onCreateViewHolder(parent: ViewGroup, viewType: 
Int): ViewHolder {
        return ViewHolder.from(parent)
    }
companion object {
   fun from(parent: ViewGroup): ViewHolder {
       val layoutInflater = LayoutInflater.from(parent.context)
       val view = layoutInflater
               .inflate(R.layout.list_item_sleep_night, parent, false)
       return ViewHolder(view)
   }
}
  1. Cambia la firma de la clase ViewHolder para que el constructor sea privado. Debido a que from() ahora es un método que muestra una nueva instancia de ViewHolder, ya no hay motivos para que alguien llame al constructor de ViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. Ejecuta la app. Esta debería compilarse y ejecutarse de la misma manera que antes, que es el resultado deseado después de refactorizar.

Proyecto de Android Studio: RecyclerViewFundamentals

  • Mostrar una lista o cuadrícula de datos es una de las tareas de IU más comunes de Android. RecyclerView está diseñado para ser eficiente incluso cuando se muestran listas muy grandes.
  • RecyclerView solo hace el trabajo necesario para procesar o dibujar los elementos que estén visibles actualmente en la pantalla.
  • Cuando un elemento se desplaza fuera de la pantalla, sus vistas se reciclan. Eso significa que el elemento se rellena con contenido nuevo que se desplaza hacia la pantalla.
  • El patrón de adaptador en la ingeniería de software ayuda a un objeto a trabajar junto con otra API. RecyclerView usa un adaptador para transformar los datos de la app en algo que puede mostrar, sin necesidad de cambiar la forma en que la app almacena y procesa los datos.

Para mostrar tus datos en un RecyclerView, necesitas las siguientes partes:

  • RecyclerView
    Para crear una instancia de RecyclerView, define un elemento <RecyclerView> en el archivo de diseño.
  • LayoutManager
    Un RecyclerView usa un LayoutManager para organizar el diseño de los elementos en RecyclerView, como acomodándolos en una cuadrícula o en una lista lineal.

    En el <RecyclerView> del archivo de diseño, configura el atributo app:layoutManager en el administrador de diseño (como LinearLayoutManager o GridLayoutManager).

    También puedes configurar LayoutManager para una RecyclerView de manera programática. Esta técnica se trata en un codelab posterior.
  • Diseño para cada elemento
    Crea un diseño para un elemento de datos en un archivo de diseño XML.
  • Adapter
    : Crea un adaptador que prepare los datos y cómo se mostrarán en una ViewHolder. Asocia el adaptador con el RecyclerView.

    Cuando se ejecute RecyclerView, usará el adaptador para descubrir cómo mostrar los datos en la pantalla.

    El adaptador requiere que implementes los siguientes métodos:
    getItemCount() para mostrar la cantidad de elementos.
    onCreateViewHolder() para mostrar el ViewHolder de un elemento de la lista.
    onBindViewHolder() para adaptar los datos a las vistas de un elemento de la lista.

  • ViewHolder
    Un ViewHolder contiene la información de vista para mostrar un elemento del diseño del elemento.
  • El método onBindViewHolder() del adaptador adapta los datos a las vistas. Siempre anulas este método. Por lo general, onBindViewHolder() aumenta el diseño de un elemento y coloca los datos en las vistas del diseño.
  • Como el RecyclerView no sabe nada sobre los datos, el Adapter debe informar al RecyclerView cuando esos datos cambian. Usa notifyDataSetChanged() para notificar a Adapter que los datos cambiaron.

Curso de Udacity:

Documentación para desarrolladores de Android:

En esta sección, se enumeran las posibles tareas para los alumnos que trabajan con este codelab como parte de un curso que dicta un instructor. Depende del instructor hacer lo siguiente:

  • Si es necesario, asigna la tarea.
  • Informa a los alumnos cómo enviar los deberes.
  • Califica las tareas.

Los instructores pueden usar estas sugerencias lo poco o lo que quieran, y deben asignar cualquier otra tarea que consideren apropiada.

Si estás trabajando en este codelab por tu cuenta, usa estas tareas para poner a prueba tus conocimientos.

Responde estas preguntas

Pregunta 1

¿Cómo muestra RecyclerView los elementos? Selecciona todas las opciones que correspondan.

▢ Muestra elementos en una lista o una cuadrícula.

▢ Se desplaza de forma vertical u horizontal.

▢ Se desplaza de forma diagonal en dispositivos más grandes, como las tablets.

▢ Permite diseños personalizados cuando una lista o una cuadrícula no alcanzan para el caso de uso determinado.

Pregunta 2

¿Cuáles son los beneficios de usar RecyclerView? Selecciona todas las opciones que correspondan.

▢ Muestra de manera eficiente listas grandes.

▢ Actualiza los datos de forma automática.

▢ Minimiza la necesidad de actualizaciones cuando un elemento se actualiza, se borra o se agrega a la lista.

▢ Reutiliza la vista que se desplaza fuera de la pantalla para mostrar el siguiente elemento que se desplaza en la pantalla.

Pregunta 3

¿Cuáles son algunos de los motivos por los que se usan adaptadores? Selecciona todas las opciones que correspondan.

▢ Separar los problemas y facilitar el cambio y la prueba de código

RecyclerView es independiente de los datos que se muestran.

▢ Las capas de procesamiento de datos no tienen que preocuparse por la forma en que se mostrarán los datos.

▢ La app se ejecutará más rápido.

Pregunta 4

¿Cuáles de las siguientes afirmaciones sobre ViewHolder son verdaderas? Selecciona todas las opciones que correspondan.

▢ El diseño ViewHolder se define en archivos de diseño XML.

▢ Hay una ViewHolder para cada unidad de datos del conjunto de datos.

▢ Puedes incluir más de un elemento ViewHolder en un elemento RecyclerView.

Adapter vincula datos con ViewHolder.

Comienza la siguiente lección: 7.2: DiffUtil y la vinculación de datos con RecyclerView