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 de forma secuencial. Todos los codelabs del curso se enumeran en la página de destino de los codelabs de Android Kotlin Fundamentals.
Introducción
En este codelab, aprenderás a usar un RecyclerView
para mostrar listas de elementos. A partir de la app de Sleep Tracker de la serie anterior de codelabs, aprenderás una forma mejor y más versátil de mostrar datos con un RecyclerView
y una arquitectura recomendada.
Conocimientos que ya deberías tener
Debes estar familiarizado con lo siguiente:
- Compilar una interfaz de usuario (IU) básica con una actividad, fragmentos y vistas
- Navegar entre fragmentos y usar
safeArgs
para pasar datos entre fragmentos - Cómo usar modelos de vistas, fábricas de modelos de vistas, transformaciones y
LiveData
y sus observadores - Crear una base de datos de
Room
, crear un DAO y definir entidades - Usar corrutinas para tareas de bases de datos y otras tareas de larga duración
Qué aprenderás
- Cómo usar un
RecyclerView
con unAdapter
y unViewHolder
para mostrar una lista de elementos
Actividades
- Cambia la app de TrackMySleepQuality de la lección anterior para usar un
RecyclerView
y mostrar los datos de calidad del sueño.
En este codelab, compilarás la parte RecyclerView
de una app que hace un seguimiento de la calidad del sueño. La app usa una base de datos Room
para almacenar los datos de sueño a lo largo del tiempo.
La app de inicio de seguimiento del sueño 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ó del usuario. La segunda pantalla, que se muestra a la derecha, es para seleccionar una calificación de la 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 noches de sueño que se muestra en la primera pantalla es funcional, pero no atractiva. La app usa un formateador complejo para crear cadenas de texto para la vista de texto y números para la calidad. Además, este diseño no se adapta a diferentes escalas. Después de solucionar 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 en Android. Las listas varían de simples a muy complejas. Una lista de vistas de texto puede mostrar 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 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 correspondiente 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 sus vistas. 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 fluida. - Cuando cambia un elemento, en lugar de volver a dibujar toda la lista,
RecyclerView
puede actualizar ese elemento. Esto representa una gran mejora 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 de adaptador
Si alguna vez viajaste entre países que usan enchufes eléctricos diferentes, probablemente sepas cómo conectar tus dispositivos a los tomacorrientes con un adaptador. El adaptador te permite convertir un tipo de enchufe en otro, lo que en realidad 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 RecyclerView
pueda mostrar, sin cambiar la forma en que la app almacena y procesa los datos. En el caso de la app de monitoreo del sueño, compilarás un adaptador que adapte los datos de la base de datos Room
a algo que RecyclerView
sepa cómo mostrar, sin cambiar ViewModel
.
Cómo implementar un RecyclerView
Para mostrar tus datos en un RecyclerView
, necesitas las siguientes partes:
- Datos que se mostrarán.
- Una instancia de
RecyclerView
definida en tu archivo de diseño para que actúe como contenedor de las vistas - Diseño para un elemento de datos.
Si todos los elementos de la lista se ven iguales, puedes usar el mismo diseño para todos, pero no es obligatorio. El diseño del elemento se debe crear por separado del diseño del fragmento, de modo que se pueda crear y completar con datos una vista de elemento a la vez. - Es un administrador de diseño.
El administrador de diseño controla la organización (el diseño) de los componentes de la IU en una vista. - Un contenedor de vistas.
El contenedor de vistas extiende la claseViewHolder
. Contiene la información de la vista para mostrar un elemento del diseño del elemento. Los contenedores de vistas también agregan información queRecyclerView
usa para mover las vistas de manera eficiente por la pantalla. - Un adaptador.
El adaptador conecta tus datos aRecyclerView
. Adapta los datos para que se puedan mostrar en unViewHolder
. UnRecyclerView
usa el adaptador para descifrar cómo mostrar los datos en la pantalla.
En esta tarea, agregarás un RecyclerView
a tu archivo de diseño y configurarás un Adapter
para exponer los datos de sueño al RecyclerView
.
Paso 1: Agrega RecyclerView con LayoutManager
En este paso, reemplazarás el ScrollView
por un RecyclerView
en el archivo fragment_sleep_tracker.xml
.
- Descarga la app RecyclerViewFundamentals-Starter desde GitHub.
- Compila y ejecuta la app. Observa cómo se muestran los datos como texto simple.
- Abre el archivo de diseño
fragment_sleep_tracker.xml
en la pestaña Design de Android Studio. - En el panel Component Tree, borra el elemento
ScrollView
. Esta acción también borra elTextView
que se encuentra dentro delScrollView
. - En el panel Palette, desplázate por la lista de tipos de componentes que se encuentra a la izquierda para encontrar Containers y, luego, selecciónalo.
- Arrastra un
RecyclerView
del panel Palette al panel Component Tree. Coloca el elementoRecyclerView
dentro del elementoConstraintLayout
.
- Si se abre un diálogo que 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 tarde unos segundos y, luego, se sincronizará tu app.
- Abre el archivo
build.gradle
del módulo, 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'
- Vuelve a cambiar a
fragment_sleep_tracker.xml
. - 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" />
- Asigna al
RecyclerView
unid
desleep_list
.
android:id="@+id/sleep_list"
- Coloca el
RecyclerView
de modo que ocupe la parte restante de la pantalla dentro delConstraintLayout
. Para ello, restringe la parte superior delRecyclerView
al botón Start, la parte inferior al botón Clear y cada lado al elemento superior. Establece el ancho y la altura del diseño en 0 dp en el editor de diseño o en XML con 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"
- Agrega un administrador de diseño al XML de
RecyclerView
. CadaRecyclerView
necesita un administrador de diseño que le indique cómo posicionar los elementos en la lista. Android proporciona unLinearLayoutManager
que, de forma predeterminada, organiza los elementos en una lista vertical de filas de ancho completo.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- Cambia a la pestaña Design y observa que las restricciones agregadas hicieron que
RecyclerView
se expandiera para ocupar el espacio disponible.
Paso 2: Crea el diseño del elemento de lista y el contenedor de TextView
El RecyclerView
es solo un contenedor. En este paso, crearás el diseño y la infraestructura para que los elementos se muestren dentro de RecyclerView
.
Para obtener un RecyclerView
funcional lo más rápido posible, al principio usarás un elemento de lista simplista que solo muestre la calidad del sueño como un número. Para ello, necesitas un elemento de ViewHolder, TextItemViewHolder
. También necesitas una vista, un TextView
, para los datos. (En un paso posterior, aprenderás más sobre los titulares de vistas y cómo diseñar todos los datos de sueño).
- 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. - En
text_item_view.xml
, borra todo el código proporcionado. - Agrega un
TextView
con un padding de16dp
al principio y al final, y un tamaño de texto de24sp
. Permite que el ancho coincida con el elemento superior y que la altura ajuste el contenido. Como esta vista se muestra dentro deRecyclerView
, no es necesario que la coloques dentro de unViewGroup
.
<?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" />
- Abre
Util.kt
. Desplázate hasta el final y agrega la definición que se muestra a continuación, que crea la claseTextItemViewHolder
. Coloca el código en la parte inferior del archivo, después de la última llave de cierre. El código va enUtil.kt
porque este elemento de diseño de la vista es temporal y lo reemplazarás más adelante.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
- Si se te solicita, importa
android.widget.TextView
yandroidx.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 del elemento y un diseño para cada elemento. Ahora puedes crear un adaptador. El adaptador crea un contenedor de vistas y lo completa con datos para que se muestre el RecyclerView
.
- En el paquete
sleeptracker
, crea una nueva clase de Kotlin llamadaSleepNightAdapter
. - Haz que la clase
SleepNightAdapter
extiendaRecyclerView.Adapter
. La clase se llamaSleepNightAdapter
porque adapta un objetoSleepNight
en algo queRecyclerView
puede usar. El adaptador necesita saber qué contenedor de vistas usar, por lo que debes pasarTextItemViewHolder
. Importa los componentes necesarios cuando se te solicite y, luego, verás un error porque hay métodos obligatorios para implementar.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
- En el nivel superior de
SleepNightAdapter
, crea una variablelistOf
SleepNight
para contener los datos.
var data = listOf<SleepNight>()
- En
SleepNightAdapter
, anulagetItemCount()
para devolver el tamaño de la lista de noches de sueño endata
. ElRecyclerView
necesita saber cuántos elementos tiene el adaptador para mostrar, y lo hace llamando agetItemCount()
.
override fun getItemCount() = data.size
- En
SleepNightAdapter
, anula la funciónonBindViewHolder()
, como se muestra a continuación.RecyclerView
llama a la funciónonBindViewHolder()
para mostrar los datos de un elemento de la lista en la posición especificada. Por lo tanto, el métodoonBindViewHolder()
toma dos argumentos: un contenedor de vistas y una posición de los datos para vincular. En esta app, el titular esTextItemViewHolder
y la posición es la posición en la lista.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
- Dentro de
onBindViewHolder()
, crea una variable para un elemento en una posición determinada en los datos.
val item = data[position]
- El
ViewHolder
que creaste tiene una propiedad llamadatextView
. Dentro deonBindViewHolder()
, establece eltext
detextView
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 lleva los datos al ViewHolder y a la pantalla.
holder.textView.text = item.sleepQuality.toString()
- En
SleepNightAdapter
, anula e implementaonCreateViewHolder()
, al que se llama cuandoRecyclerView
necesita un contenedor de vistas para representar un elemento.
Esta función toma dos parámetros y devuelve unViewHolder
. El parámetroparent
, que es el grupo de vistas que contiene el elemento de diseño de la vista, siempre esRecyclerView
. El parámetroviewType
se usa cuando hay varias vistas en el mismoRecyclerView
. Por ejemplo, si colocas una lista de vistas de texto, una imagen y un video en el mismoRecyclerView
, la funciónonCreateViewHolder()
debería saber qué tipo de vista usar.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
- En
onCreateViewHolder()
, crea una instancia deLayoutInflater
.
El inflador de diseño sabe cómo crear vistas a partir de diseños en XML. El campocontext
contiene información sobre cómo inflar correctamente la vista. En un adaptador para una vista de reciclador, siempre pasas el contexto del grupo de vistasparent
, que es elRecyclerView
.
val layoutInflater = LayoutInflater.from(parent.context)
- En
onCreateViewHolder()
, crea elview
pidiéndole allayoutinflater
que lo expanda.
Pasa el diseño XML para la vista y el grupo de vistasparent
para la vista. El tercer argumento booleano esattachToRoot
. Este argumento debe serfalse
, porqueRecyclerView
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
- En
onCreateViewHolder()
, devuelve unTextItemViewHolder
creado conview
.
return TextItemViewHolder(view)
- El adaptador debe informar al
RecyclerView
cuando eldata
cambió, ya que elRecyclerView
no sabe nada sobre los datos. Solo conoce los titulares de vistas que le proporciona el adaptador.
Para indicarle alRecyclerView
cuándo cambiaron los datos que muestra, agrega un setter personalizado a la variabledata
que se encuentra en la parte superior de la claseSleepNightAdapter
. En el setter, asigna un valor nuevo adata
y, luego, llama anotifyDataSetChanged()
para activar el redibujo 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 RecyclerView
necesita saber qué adaptador usar para obtener contenedores de vistas.
- Abre
SleepTrackerFragment.kt
. - En
onCreateview()
, crea un adaptador. Coloca este código después de la creación del modeloViewModel
y antes de la instrucciónreturn
.
val adapter = SleepNightAdapter()
- Asocia el
adapter
con elRecyclerView
.
binding.sleepList.adapter = adapter
- Limpia y vuelve a compilar tu proyecto para actualizar el objeto
binding
.
Si sigues viendo errores relacionados conbinding.sleepList
obinding.FragmentSleepTrackerBinding
, invalida las memorias caché y reinicia el sistema. (Selecciona File > Invalidate Caches / Restart).
Si ejecutas la app ahora, no habrá errores, pero no verás ningún dato cuando presiones Start y, luego, Stop.
Paso 5: Obtén datos en el adaptador
Hasta ahora, tienes un adaptador y una forma de obtener datos del adaptador en el RecyclerView
. Ahora, debes obtener datos en el adaptador desde ViewModel
.
- Abre
SleepTrackerViewModel
. - Busca la variable
nights
, que almacena todas las noches de sueño, que son los datos que se mostrarán. La variablenights
se establece llamando agetAllNights()
en la base de datos. - Quita
private
denights
, ya que crearás un observador que necesita acceder a esta variable. Tu declaración debería verse de la siguiente manera:
val nights = database.getAllNights()
- En el paquete
database
, abreSleepDatabaseDao
. - Busca la función
getAllNights()
. Ten en cuenta que esta función devuelve una lista de valoresSleepNight
comoLiveData
. Esto significa que la variablenights
contieneLiveData
, queRoom
mantiene actualizada, y puedes observarnights
para saber cuándo cambia. - Abre
SleepTrackerFragment
. - En
onCreateView()
, debajo de la creación deadapter
, crea un observador en la variablenights
.
Si proporcionas elviewLifecycleOwner
del fragmento como propietario del ciclo de vida, puedes asegurarte de que este observador solo esté activo cuando elRecyclerView
esté en la pantalla.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})
- Dentro del observador, cada vez que obtengas un valor no nulo (para
nights
), asígnale el valor adata
del adaptador. Este es el código completado para el observador y la configuración de los datos:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
- Compila y ejecuta tu código.
Si el adaptador funciona, verás los números de calidad del sueño como una lista. En la captura de pantalla de la izquierda, se muestra -1 después de que presionas Start. En la captura de pantalla de la derecha, se muestra el número actualizado de la calidad del sueño después de que presionas Detener y seleccionas una calificación de calidad.
Paso 6: Explora cómo se reciclan los titulares 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 para la vista que está a punto de desplazarse en la pantalla.
Como estos contenedores de vistas se reciclan, asegúrate de que onBindViewHolder()
establezca o restablezca cualquier personalización que los elementos anteriores hayan establecido en un contenedor de vistas.
Por ejemplo, puedes establecer el color del texto en rojo en los titulares de vistas que contienen calificaciones de calidad menores o iguales a 1 y representan un sueño deficiente.
- En la clase
SleepNightAdapter
, agrega el siguiente código al final deonBindViewHolder()
.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
}
- Ejecuta la app.
- Si agregas algunos datos de baja calidad del sueño, el número se mostrará en rojo.
- Agrega calificaciones altas para la calidad del sueño hasta que veas un número rojo alto en la pantalla.
ComoRecyclerView
reutiliza los contenedores de vistas, eventualmente reutiliza uno de los contenedores de vistas rojos para una calificación de alta calidad. La calificación alta se muestra de forma errónea en rojo.
- Para corregir este problema, agrega una instrucción
else
para establecer el color en negro si la calidad no es menor o igual a uno.
Con ambas condiciones explícitas, el titular de la vista 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
}
- Ejecuta la app y los números siempre deberían tener el color correcto.
¡Felicitaciones! Ahora tienes un RecyclerView
básico completamente funcional.
En esta tarea, reemplazarás el titular de vista simple por uno que pueda mostrar más datos de una noche de sueño.
El ViewHolder
simple que agregaste a Util.kt
solo une un 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 mucha funcionalidad. Un objeto ViewHolder
describe una vista de elemento y los metadatos sobre su lugar dentro del objeto RecyclerView
. RecyclerView
depende de esta funcionalidad para posicionar correctamente la vista a medida que se desplaza la lista y para hacer cosas interesantes, como animar vistas cuando se agregan o quitan elementos en el Adapter
.
Si RecyclerView
necesita acceder a las vistas almacenadas en ViewHolder
, puede hacerlo con la propiedad itemView
del contenedor de vistas. RecyclerView
usa itemView
cuando vincula un elemento para mostrarlo 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 un ImageView
para la calidad del sueño, un TextView
para la duración del sueño y un TextView
para la calidad como texto. Como ya creaste diseños antes, copia y pega el código XML proporcionado.
- Crea un nuevo archivo de recursos de diseño y asígnale el nombre
list_item_sleep_night
. - Reemplaza todo el código del archivo por 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>
- Cambia 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 a continuación. En la vista de esquema, se ve como la captura de pantalla de la derecha.
Paso 2: Crea ViewHolder
- Abre
SleepNightAdapter.kt
. - Crea una clase dentro de
SleepNightAdapter
llamadaViewHolder
y haz que extiendaRecyclerView.ViewHolder
.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
- Dentro de
ViewHolder
, obtén referencias a las vistas. Necesitas una referencia a las vistas que actualizará esteViewHolder
. Cada vez que vinculas esteViewHolder
, debes acceder a la imagen y a ambas vistas de texto. (Convertirás 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
- En la definición de
SleepNightAdapter
, en lugar deTextItemViewHolder
, usa elSleepNightAdapter.ViewHolder
que acabas de crear.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {
Actualiza onCreateViewHolder()
con este comando:
- Cambia la firma de
onCreateViewHolder()
para que muestreViewHolder
. - Cambia el inflador de diseño para que use el recurso de diseño correcto,
list_item_sleep_night
. - Quita la transmisión a
TextView
. - En lugar de devolver un
TextItemViewHolder
, devuelve unViewHolder
.
Esta es la funciónonCreateViewHolder()
actualizada finalizada:
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:
- Cambia la firma de
onBindViewHolder()
para que el parámetroholder
sea unViewHolder
en lugar de unTextItemViewHolder
. - Dentro de
onBindViewHolder()
, borra todo el código, excepto la definición deitem
. - Define un
val
res
que contenga una referencia alresources
de esta vista.
val res = holder.itemView.context.resources
- Establece el texto de la vista de texto
sleepLength
en la duración. Copia el siguiente código, que llama a una función de formato que se proporciona con el código de partida.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
- Esto genera un error porque se debe definir
convertDurationToFormatted()
. AbreUtil.kt
y quita el comentario del código y las importaciones asociadas. (Selecciona Code > Comment with Line comments). - De vuelta en
onBindViewHolder()
, usaconvertNumericQualityToString()
para establecer la calidad.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
- Es posible que debas importar estas funciones de forma manual.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
- Establece el ícono correcto para la calidad. El nuevo ícono
ic_sleep_active
se proporciona en el código de partida.
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
})
- Esta es la función
onBindViewHolder()
actualizada finalizada, que establece todos los datos paraViewHolder
:
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
})
}
- Ejecuta la app. La pantalla debería verse como la siguiente captura, en la que se muestra el ícono de calidad del sueño junto con el texto de la duración y la calidad del sueño.
¡Se completó tu RecyclerView
! Aprendiste a implementar un Adapter
y un ViewHolder
, y los combinaste para mostrar una lista con un RecyclerView
Adapter
.
Hasta ahora, tu código muestra el proceso de creación de un adaptador y un contenedor de vistas. Sin embargo, puedes mejorar este código. El código para mostrar y el código para administrar los titulares de vistas están mezclados, y onBindViewHolder()
conoce los detalles sobre cómo actualizar el ViewHolder
.
En una app de producción, es posible que tengas varios titulares de vistas, adaptadores más complejos y varios desarrolladores que realizan cambios. Debes estructurar tu código de manera que todo lo relacionado con un contenedor de vistas se encuentre solo en el contenedor de vistas.
Paso 1: Refactoriza onBindViewHolder()
En este paso, refactorizarás el código y moverás toda la funcionalidad del titular de la vista a ViewHolder
. El objetivo de esta refactorización no es cambiar la apariencia de la app para el usuario, sino facilitar y hacer más seguro el trabajo de los desarrolladores en el código. Afortunadamente, Android Studio tiene herramientas para ayudarte.
- En
SleepNightAdapter
, enonBindViewHolder()
, selecciona todo, excepto la sentencia para declarar la variableitem
. - Haz clic con el botón derecho y, luego, selecciona Refactor > Extract > Function.
- Nombra la función
bind
y acepta los parámetros sugeridos. Haz clic en OK.
La funciónbind()
se coloca debajo deonBindViewHolder()
.
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
})
}
- Coloca el cursor sobre la palabra
holder
del parámetroholder
debind()
. PresionaAlt+Enter
(Option+Enter
en Mac) para abrir el menú de intención. Selecciona Convert parameter to receiver para convertirlo en una función de extensión que tenga la siguiente firma:
private fun ViewHolder.bind(item: SleepNight) {...}
- Corta y pega la función
bind()
en el archivoViewHolder
. - Haz que
bind()
sea público. - Si es necesario, importa
bind()
al adaptador. - Como ahora está en
ViewHolder
, puedes quitar la parte deViewHolder
de la firma. Este es el código final de la funciónbind()
en la claseViewHolder
.
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
Actualmente, el método onCreateViewHolder()
del adaptador aumenta la vista desde el recurso de diseño para el ViewHolder
. Sin embargo, la inflación no tiene nada que ver con el adaptador, sino con el ViewHolder
. La inflación debería ocurrir en el ViewHolder
.
- En
onCreateViewHolder()
, selecciona todo el código del cuerpo de la función. - Haz clic con el botón derecho y, luego, selecciona Refactor > Extract > Function.
- Nombra la función
from
y acepta los parámetros sugeridos. Haz clic en OK. - Coloca el cursor sobre el nombre de la función
from
. PresionaAlt+Enter
(Option+Enter
en Mac) para abrir el menú de intención. - Selecciona Mover al objeto complementario. La función
from()
debe estar en un objeto complementario para que se pueda llamar en la claseViewHolder
, no en una instancia deViewHolder
. - Mueve el objeto
companion
a la claseViewHolder
. - Haz que
from()
sea público. - En
onCreateViewHolder()
, cambia la instrucciónreturn
para que muestre el resultado de llamar afrom()
en la claseViewHolder
.
Tus métodosonCreateViewHolder()
yfrom()
completados deberían ser similares al siguiente código, y tu código debería compilarse y ejecutarse 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)
}
}
- Cambia la firma de la clase
ViewHolder
para que el constructor sea privado. Dado quefrom()
ahora es un método que devuelve una nueva instancia deViewHolder
, ya no hay motivos para que nadie llame al constructor deViewHolder
.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
- Ejecuta la app. Debería compilarse y ejecutarse de la misma manera que antes, que es el resultado deseado después de la refactorización.
Proyecto de Android Studio: RecyclerViewFundamentals
- Mostrar una lista o cuadrícula de datos es una de las tareas de IU más comunes en Android.
RecyclerView
está diseñado para ser eficiente incluso cuando se muestran listas extremadamente grandes. RecyclerView
solo realiza el trabajo necesario para procesar o dibujar elementos que estén visibles actualmente en la pantalla.- Cuando un elemento se desplaza fuera de la pantalla, se reciclan sus vistas. Esto 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 que un objeto funcione junto con otra API.
RecyclerView
usa un adaptador para transformar los datos de la app en algo que pueda 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 deRecyclerView
, define un elemento<RecyclerView>
en el archivo de diseño. - LayoutManager
UnRecyclerView
usa unLayoutManager
para organizar el diseño de los elementos en elRecyclerView
, por ejemplo, para diseñarlos en una cuadrícula o en una lista lineal.
En el<RecyclerView>
del archivo de diseño, establece el atributoapp:layoutManager
en el administrador de diseño (comoLinearLayoutManager
oGridLayoutManager
).
También puedes establecer elLayoutManager
para unRecyclerView
de forma programática. (Esta técnica se explica 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 la forma en que se mostrarán en unViewHolder
. Asocia el adaptador con elRecyclerView
.
Cuando se ejecutaRecyclerView
, se usa el adaptador para determinar cómo mostrar los datos en la pantalla.
El adaptador requiere que implementes los siguientes métodos:
–getItemCount()
para devolver la cantidad de elementos.
–onCreateViewHolder()
para devolver elViewHolder
de un elemento en la lista.
–onBindViewHolder()
para adaptar los datos a las vistas de un elemento en la lista. - ViewHolder
UnViewHolder
contiene la información de la 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()
infla el diseño de un elemento y coloca los datos en las vistas del diseño. - Dado que
RecyclerView
no sabe nada sobre los datos,Adapter
debe informar aRecyclerView
cuando cambien esos datos. UsanotifyDataSetChanged()
para notificar alAdapter
que los datos cambiaron.
Curso de Udacity:
Documentación para desarrolladores de Android:
En esta sección, se enumeran las posibles actividades para el hogar para los alumnos que trabajan en este codelab como parte de un curso dirigido por un instructor. Depende del instructor hacer lo siguiente:
- Si es necesario, asigna una tarea.
- Comunicarles a los alumnos cómo enviar las actividades para el hogar.
- Califica las actividades para el hogar.
Los instructores pueden usar estas sugerencias en la medida que quieran y deben asignar cualquier otra actividad para el hogar que consideren apropiada.
Si estás trabajando en este codelab por tu cuenta, usa estas actividades para el hogar para probar tus conocimientos.
Responde estas preguntas:
Pregunta 1
¿Cómo muestra elementos RecyclerView
? Selecciona todas las opciones que correspondan.
▢ Muestra los elementos en una lista o una cuadrícula.
▢ Se desplaza de forma horizontal o vertical.
▢ Se desplaza de manera diagonal en dispositivos más grandes, como las tablets.
▢ Permite el uso de 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 listas extensas de manera eficiente.
▢ Actualiza los datos automáticamente.
▢ 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 algunas de las razones para usar adaptadores? Selecciona todas las opciones que correspondan.
▢ La separación de problemas facilita el cambio y las pruebas del código.
▢ RecyclerView
es independiente de los datos que se muestran.
▢ Las capas de procesamiento de datos no necesitan preocuparse por cómo se muestran 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 de ViewHolder
se define en los archivos de diseño XML.
▢ Hay un ViewHolder
para cada unidad de datos en el conjunto de datos.
▢ Puedes tener más de un ViewHolder
en un RecyclerView
.
▢ Adapter
vincula los datos a ViewHolder
.
Comienza la siguiente lección: