¿Por qué RecyclerView?
RecyclerView es el componente estándar para mostrar listas en Android. A diferencia del viejo ListView, recicla las Views que salen de pantalla para mostrar nuevos items sin crear objetos nuevos constantemente. En listas con cientos o miles de items, la diferencia de rendimiento es enorme.
Las tres partes de un RecyclerView
- RecyclerView: el widget en el XML que muestra la lista.
- LayoutManager: decide cómo se organizan los items (vertical, horizontal, grilla).
- Adapter: conecta la lista de datos con las Views. Es el componente central que vas a escribir.
ViewHolder — el patrón base
El ViewHolder guarda referencias a las Views de un item para no tener que buscarlas repetidamente con findViewById (que es lento). Cada item de la lista tiene su propio ViewHolder:
// El layout del item: res/layout/item_producto.xml
// (Un CardView con un TextView para nombre y otro para precio)
class ProductoViewHolder(
private val binding: ItemProductoBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(producto: Producto, onClick: (Producto) -> Unit) {
binding.tvNombre.text = producto.nombre
binding.tvPrecio.text = "$${producto.precio}"
binding.root.setOnClickListener { onClick(producto) }
}
companion object {
fun create(parent: ViewGroup): ProductoViewHolder {
val binding = ItemProductoBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return ProductoViewHolder(binding)
}
}
}
DiffUtil — actualizaciones eficientes
DiffUtil calcula la diferencia entre dos listas y aplica solo las animaciones necesarias (insert, remove, change) en lugar de redibujar toda la lista. Necesitás un DiffUtil.ItemCallback:
class ProductoDiffCallback : DiffUtil.ItemCallback<Producto>() {
// ¿Es el mismo objeto en la lista? (comparar ID)
override fun areItemsTheSame(old: Producto, new: Producto): Boolean {
return old.id == new.id
}
// ¿El contenido del objeto cambió? (comparar todos los campos)
override fun areContentsTheSame(old: Producto, new: Producto): Boolean {
return old == new // funciona si Producto es data class
}
}
ListAdapter — el adapter moderno
ListAdapter combina el Adapter con DiffUtil automáticamente. Solo le pasás la nueva lista y él calcula las diferencias en un thread de background:
class ProductosAdapter(
private val onClick: (Producto) -> Unit
) : ListAdapter<Producto, ProductoViewHolder>(ProductoDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductoViewHolder {
return ProductoViewHolder.create(parent)
}
override fun onBindViewHolder(holder: ProductoViewHolder, position: Int) {
holder.bind(getItem(position), onClick)
}
}
getItem(position)En ListAdapter siempre usá getItem(position) en lugar de acceder directamente a una lista propia. ListAdapter administra la lista internamente y coordina con DiffUtil.
Configurar todo en el Fragment
class ProductosFragment : Fragment(R.layout.fragment_productos) {
private val viewModel: ProductosViewModel by viewModels()
private val adapter = ProductosAdapter { producto ->
// Lambda de click: navegar al detalle
findNavController().navigate(
ProductosFragmentDirections.actionProductosToDetalle(producto.id)
)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Configurar RecyclerView
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = [email protected]
// Optimización: si el tamaño de la lista no cambia el tamaño del RecyclerView
setHasFixedSize(true)
}
// Observar datos y pasarlos al adapter
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
if (state is ProductosUiState.Success) {
adapter.submitList(state.productos)
}
}
}
}
}
}
LayoutManagers
// Lista vertical (el más común)
LinearLayoutManager(context)
// Lista horizontal
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
// Grilla de 2 columnas
GridLayoutManager(context, 2)
// Grilla de ancho variable (tipo Pinterest)
StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)