RecyclerView es el componente estándar de Android para mostrar listas largas de forma eficiente, reutilizando las vistas que ya no están visibles en pantalla.
Cuando tienes 500 productos de Liverpool o 1,000 pedidos de Mercado Libre, no puedes crear una vista por cada elemento. RecyclerView resuelve ese problema reciclando las filas que el usuario ya scrolleó.
¿Por qué RecyclerView y no ListView?
ListView es el componente anterior y no recicla vistas de forma automática ni eficiente. RecyclerView obliga a usar el patrón ViewHolder, que mantiene referencias a las vistas en memoria y evita llamadas costosas a findViewById() en cada scroll.
El resultado es una lista fluida incluso con miles de elementos.
Las tres piezas principales
RecyclerView funciona con tres componentes que trabajan juntos:
| Componente | Responsabilidad |
|---|---|
| RecyclerView | Contenedor en el layout XML |
| ViewHolder | Guarda referencias a las vistas de cada fila |
| Adapter | Une los datos con el ViewHolder |
No puedes tener uno sin los otros dos.
Paso 1: Agregar la dependencia
Abre tu archivo build.gradle (Module: app) y agrega:
dependencies {
implementation "androidx.recyclerview:recyclerview:1.3.2"
}
Sincroniza el proyecto con Sync Now. Sin esta línea, Android Studio no reconocerá el componente.
Paso 2: Declarar RecyclerView en el layout
En tu archivo activity_main.xml, agrega el componente:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerProductos"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp" />
Este RecyclerView ocupará toda la pantalla y mostrará la lista de productos.
Paso 3: Crear el layout de cada fila
Crea un nuevo archivo XML llamado item_producto.xml en la carpeta res/layout/:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:id="@+id/tvNombreProducto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPrecioProducto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#2E7D32" />
</LinearLayout>
Este layout define cómo se ve una sola fila de la lista. Cada fila muestra nombre y precio.
Paso 4: Crear el modelo de datos
Antes de construir el Adapter, necesitas una clase que represente cada elemento. Crea una data class:
data class Producto(
val nombre: String,
val precio: Double
)
Esta clase modela un producto con nombre y precio, como los que vende Liverpool o Bimbo en sus apps.
Paso 5: Crear el Adapter con ViewHolder
Esta es la parte central. Crea una nueva clase ProductoAdapter.kt:
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class ProductoAdapter(
private val productos: List<Producto>
) : RecyclerView.Adapter<ProductoAdapter.ProductoViewHolder>() {
// ViewHolder: guarda referencias a las vistas de una fila
inner class ProductoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tvNombre: TextView = itemView.findViewById(R.id.tvNombreProducto)
val tvPrecio: TextView = itemView.findViewById(R.id.tvPrecioProducto)
}
// Infla el layout de la fila y crea el ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductoViewHolder {
val vista = LayoutInflater.from(parent.context)
.inflate(R.layout.item_producto, parent, false)
return ProductoViewHolder(vista)
}
// Llena las vistas con los datos del elemento en la posición dada
override fun onBindViewHolder(holder: ProductoViewHolder, position: Int) {
val producto = productos[position]
holder.tvNombre.text = producto.nombre
holder.tvPrecio.text = "$${String.format("%,.0f", producto.precio)}"
}
// Devuelve el total de elementos en la lista
override fun getItemCount(): Int = productos.size
}
El Adapter tiene tres métodos obligatorios: onCreateViewHolder, onBindViewHolder y getItemCount. Si omites uno, el código no compilará.
Paso 6: Conectar todo en la Activity
En tu MainActivity.kt, inicializa el RecyclerView con el Adapter y un LayoutManager:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Lista de productos con precios en pesos
val productos = listOf(
Producto("Laptop Bimbo Edition", 18500.0),
Producto("Pantalla Liverpool 55\"", 12300.0),
Producto("Bocina FEMSA Pro", 3200.0),
Producto("Teclado Mercado Libre", 850.0),
Producto("Mouse inalámbrico", 450.0)
)
val recycler = findViewById<RecyclerView>(R.id.recyclerProductos)
// LinearLayoutManager muestra los elementos uno debajo del otro
recycler.layoutManager = LinearLayoutManager(this)
recycler.adapter = ProductoAdapter(productos)
}
}
Al correr la app, verás cinco productos listados con sus precios formateados. El output en pantalla se ve así:
Laptop Bimbo Edition
$18,500
Pantalla Liverpool 55"
$12,300
Bocina FEMSA Pro
$3,200
Teclado Mercado Libre
$850
Mouse inalámbrico
$450
Agregar clic en cada elemento
Una lista sin interacción es poco útil. Agrega un listener al Adapter para detectar qué producto tocó el usuario:
class ProductoAdapter(
private val productos: List<Producto>,
private val onItemClick: (Producto) -> Unit
) : RecyclerView.Adapter<ProductoAdapter.ProductoViewHolder>() {
inner class ProductoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tvNombre: TextView = itemView.findViewById(R.id.tvNombreProducto)
val tvPrecio: TextView = itemView.findViewById(R.id.tvPrecioProducto)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductoViewHolder {
val vista = LayoutInflater.from(parent.context)
.inflate(R.layout.item_producto, parent, false)
return ProductoViewHolder(vista)
}
override fun onBindViewHolder(holder: ProductoViewHolder, position: Int) {
val producto = productos[position]
holder.tvNombre.text = producto.nombre
holder.tvPrecio.text = "$${String.format("%,.0f", producto.precio)}"
// Asigna el clic al layout completo de la fila
holder.itemView.setOnClickListener {
onItemClick(producto)
}
}
override fun getItemCount(): Int = productos.size
}
En la Activity, pasa la lambda al crear el Adapter:
recycler.adapter = ProductoAdapter(productos) { producto ->
// Muestra el nombre del producto seleccionado
Toast.makeText(this, "Seleccionaste: ${producto.nombre}", Toast.LENGTH_SHORT).show()
}
Este patrón de lambda como callback es el más común en proyectos profesionales de Android en México y en todo el mundo.
Errores comunes
Error 1: Olvidar asignar el LayoutManager.
Si no escribes recycler.layoutManager = LinearLayoutManager(this), la lista no mostrará nada. El RecyclerView necesita saber cómo organizar los elementos.
Error 2: No inflar el layout correcto en onCreateViewHolder.
Si usas R.layout.activity_main en lugar de R.layout.item_producto, la app mostrará vistas incorrectas o lanzará una excepción en tiempo de ejecución.
Error 3: Modificar la lista original fuera del Adapter sin notificar.
Si agregas o quitas elementos a productos sin llamar a notifyDataSetChanged() o notifyItemInserted(position), la pantalla no se actualiza. El Adapter no detecta cambios automáticamente en una List simple.
Error 4: Hacer operaciones pesadas dentro de onBindViewHolder.
onBindViewHolder se llama constantemente mientras el usuario hace scroll. Cálculos complejos o llamadas a red en ese método generan lag visible. Prepara los datos antes de pasarlos al Adapter.
Tipos de LayoutManager disponibles
| LayoutManager | Resultado visual |
|---|---|
LinearLayoutManager |
Lista vertical u horizontal |
GridLayoutManager(ctx, 2) |
Cuadrícula de 2 columnas |
StaggeredGridLayoutManager |
Cuadrícula con alturas variables |
Para un catálogo de productos como el de Liverpool, GridLayoutManager con 2 columnas es la opción más común.