Un ListView es un widget especializado en mostrar listas desplazables de elementos.
En la entrada anterior vimos que SingleChildScrollView es útil, pero peligroso para el rendimiento si tenemos mucho contenido. Hoy vamos a conocer a su hermano mayor, el especialista en colecciones: el ListView.
Las listas son el componente más común en las aplicaciones móviles. Piensa en WhatsApp (lista de chats), Instagram (feed de fotos) o Spotify (lista de canciones). Todas usan el mismo principio.
Pero en Flutter, no todos los ListView son iguales. Elegir el constructor incorrecto puede hacer que tu App vaya a tirones. Vamos a ver por qué.
ListView con hijos explícitos
La forma más básica de usar este widget es con su constructor por defecto. Es muy similar a usar una Column dentro de un scroll, pero con menos código.
ListView(
children: [
ListTile(title: Text("Opción 1"), leading: Icon(Icons.map)),
ListTile(title: Text("Opción 2"), leading: Icon(Icons.photo)),
ListTile(title: Text("Opción 3"), leading: Icon(Icons.phone)),
// ... imagina 50 opciones más
],
)
He usado el widget ListTile. Es un widget prefabricado de Flutter que sigue las guías de Material Design para listas: tiene título, subtítulo, icono a la izquierda (leading) e icono a la derecha (trailing).
El problema del ListView estático
Aunque es mejor que SingleChildScrollView, el ListView normal tiene el mismo “defecto”: intenta renderizar a sus hijos de una vez.
Es perfecto para un menú de ajustes con 10 o 20 opciones fijas. Pero si intentas cargar ahí una lista de 5.000 productos de una base de datos, tu memoria RAM sufrirá.
Construcción Lazy
Imagina que tienes una lista de 1.000 elementos, pero en la pantalla de tu móvil solo caben 10. ¿Tiene sentido gastar batería y memoria pintando los 990 que no se ven? No.
Flutter soluciona esto dibujando solo lo que está en el “Viewport” (lo que se ve en pantalla) más un pequeño margen de seguridad. A medida que haces scroll, Flutter destruye los elementos que salen por arriba y crea los que entran por abajo.
Para usar esta técnica, necesitamos el constructor ListView.builder.
ListView.builder
Este constructor no recibe una lista de hijos (children). En su lugar, le damos “instrucciones” de cómo fabricar cada elemento bajo demanda.
Necesita dos parámetros clave:
itemCount: Cuántos elementos hay en total.itemBuilder: Una función que se ejecuta cada vez que un elemento va a entrar en pantalla.
Cómo funciona el itemBuilder
Esta función recibe el context y un index (un número entero que indica qué posición de la lista se está dibujando: 0, 1, 2…).
final listaNombres = ["Ana", "Luis", "Carlos", "Marta", "Pedro", ...];
ListView.builder(
itemCount: listaNombres.length, // Le decimos el total (ej: 100)
itemBuilder: (context, index) {
// Esta función se ejecuta en bucle, pero SOLO para los visibles.
// Usamos el 'index' para acceder a los datos.
final nombre = listaNombres[index];
return ListTile(
leading: CircleAvatar(child: Text(nombre[0])), // Primera letra
title: Text(nombre),
subtitle: Text("Posición en la lista: $index"),
);
},
)
Si ejecutas este código con una lista de 10.000 nombres, la App irá totalmente fluida. Flutter solo estará gestionando los ~10 widgets que caben en tu pantalla en ese instante.
ListView.separated para añadir separadores
A menudo, en los diseños nos piden poner una línea gris (Divider) entre cada elemento para separarlos visualmente.
Podríamos intentar meter el Divider dentro del itemBuilder, pero eso pondría una línea también al final del último elemento (o tendríamos que hacer lógica fea con if).
Flutter nos regala el constructor ListView.separated.
ListView.separated(
itemCount: 50,
// Constructor del item normal
itemBuilder: (context, index) {
return ListTile(title: Text("Elemento $index"));
},
// Constructor del separador (se intercala automáticamente)
separatorBuilder: (context, index) {
return Divider(color: Colors.blueAccent);
},
)
Esto dibujará: Item 0 -> Separador -> Item 1 -> Separador -> Item 2…
Errores comunes
No poner límites de tamaño
Al igual que con Column, un ListView intenta ocupar todo el alto posible. Si metes un ListView dentro de otro ListView o dentro de una Column sin usar Expanded, tendrás errores de tamaño infinito.
Olvidar itemCount
Si en ListView.builder no especificas el itemCount, la lista será infinita. Esto no es necesariamente un error (a veces queremos scroll infinito), pero si accedes a miLista[index] y el índice supera el tamaño de tu array, la App crasheará (RangeError).
No usar claves al reordenar
Si vas a permitir que el usuario borre o mueva elementos de la lista, cada item necesita una Key única para que Flutter no se líe reciclando los widgets. (Esto lo veremos en profundidad en módulos avanzados, pero tenlo en mente).
Qué constructor usar
| Constructor | ¿Cuándo usarlo? | Ejemplo |
|---|---|---|
ListView | Listas pequeñas y conocidas de antemano. | Menú de opciones, Settings. |
ListView.builder | Listas grandes, desconocidas o que vienen de una API. | Feed de noticias, Lista de contactos. |
ListView.separated | Igual que el builder, pero necesitas separadores. | Lista de emails con líneas divisorias. |