ListView.Builder en Flutter
Crea listas eficientes, dinámicas y de alto rendimiento
Descargar Ejemplos Completos¿Qué es ListView.Builder?
El widget ListView.Builder en Flutter es la forma más eficiente de crear listas con muchos elementos, ya que construye los widgets bajo demanda según se necesitan.
ListView Básico
- ❌ Construye todos los items al mismo tiempo
- ❌ Consumo alto de memoria
- ❌ Lento con muchos elementos
- ❌ No escalable
ListView.Builder
- ✅ Construye items bajo demanda
- ✅ Memoria optimizada
- ✅ Rápido incluso con miles de items
- ✅ Altamente escalable
Ventajas de ListView.Builder:
- ✅ Lazy Loading: Solo construye los elementos visibles
- ✅ Alto Rendimiento: Ideal para listas grandes
- ✅ Memoria Optimizada: No carga todos los datos a la vez
- ✅ Dinámico: Fácil de actualizar y modificar
- ✅ Flexible: Personalización total de cada item
¿Cuándo usar ListView.Builder?
Listas Grandes
Cuando tienes muchos elementos (50+)
Datos Dinámicos
Cuando los datos vienen de APIs o bases de datos
Contenido Cambiante
Cuando la lista puede crecer o cambiar
Apps de Alto Rendimiento
Cuando necesitas optimizar el rendimiento
Implementación Básica
ListView.Builder básico con lista de elementos
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ListView.Builder Example',
home: const ListViewExample(),
);
}
}
class ListViewExample extends StatelessWidget {
const ListViewExample({super.key});
// Lista de datos de ejemplo
final List items = [
'Manzana', 'Banana', 'Naranja', 'Uva', 'Fresa',
'Piña', 'Mango', 'Pera', 'Kiwi', 'Sandía',
'Melón', 'Durazno', 'Cereza', 'Limón', 'Lima'
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lista de Frutas'),
backgroundColor: Colors.green,
),
body: ListView.builder(
itemCount: items.length, // Número total de elementos
itemBuilder: (context, index) {
// Este builder se llama solo para los items visibles
final fruit = items[index];
final color = index % 2 == 0 ? Colors.green[50] : Colors.white;
return Container(
margin: const EdgeInsets.symmetric(vertical: 2, horizontal: 8),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(8),
),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.green[100],
child: Text(
'${index + 1}',
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
),
title: Text(
fruit,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
subtitle: Text('Fruta número ${index + 1}'),
trailing: const Icon(Icons.chevron_right, color: Colors.green),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Seleccionaste: $fruit'),
duration: const Duration(seconds: 1),
),
);
},
),
);
},
),
);
}
}
Propiedades de ListView.Builder
itemCount
Número total de elementos en la lista
Tipo: int?
Requerido: No (pero recomendado)
Por defecto: null (lista infinita)
itemBuilder
Función que construye cada elemento de la lista
Tipo: Widget? Function(BuildContext, int)
Requerido: Sí
scrollDirection
Dirección del desplazamiento
Tipo: Axis
Valores: Axis.vertical, Axis.horizontal
Por defecto: Axis.vertical
padding
Espaciado interno de la lista
Tipo: EdgeInsets?
Por defecto: null
physics
Comportamiento físico del scroll
Tipo: ScrollPhysics?
Valores comunes: AlwaysScrollableScrollPhysics(), BouncingScrollPhysics(), NeverScrollableScrollPhysics()
shrinkWrap
Si la lista debe ajustarse a su contenido
Tipo: bool
Por defecto: false
Uso: Para listas dentro de Column
Ejemplos Visuales
1. Lista Básica con Diferentes Estilos
ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
final task = tasks[index];
return AnimatedContainer(
duration: Duration(milliseconds: 300),
margin: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
decoration: BoxDecoration(
color: index % 2 == 0 ? Colors.blue[50] : Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 2,
offset: Offset(0, 1),
),
],
),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blue[100],
child: Text('${index + 1}'),
),
title: Text(
task.title,
style: TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text(task.subtitle),
trailing: Icon(
task.completed ? Icons.check_circle : Icons.radio_button_unchecked,
color: task.completed ? Colors.green : Colors.grey,
),
onTap: () => _toggleTask(index),
),
);
},
)
2. Lista Horizontal con Tarjetas
ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: products.length,
padding: EdgeInsets.all(16),
itemBuilder: (context, index) {
final product = products[index];
return Container(
width: 150,
margin: EdgeInsets.only(right: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(product.icon, size: 48, color: Colors.blue),
SizedBox(height: 12),
Text(
product.name,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
textAlign: TextAlign.center,
),
SizedBox(height: 8),
Text(
'\$${product.price}',
style: TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
],
),
);
},
)
Demo Interactivo de ListView.Builder
Demo ListView.Builder
0 items visibles
// El código se generará aquí
Mejores Prácticas
✅ Lo que SÍ debes hacer
- Usa itemCount para listas con tamaño conocido
- Mantén los itemBuilders simples y eficientes
- Usa const widgets cuando sea posible
- Considera usar ListView.separated para separadores
- Optimiza las imágenes con cache y tamaño apropiado
❌ Lo que NO debes hacer
- No crees widgets complejos en el itemBuilder
- No uses setState dentro del itemBuilder
- No olvides el itemCount en listas grandes
- No uses ListView.builder para listas pequeñas fijas
- No cargues datos pesados en el builder
Tip Profesional
Para listas extremadamente grandes (1000+ items), considera usar ListView.builder con addAutomaticKeepAlives: false y addRepaintBoundaries: false para máximo rendimiento, pero solo si no necesitas preservar el estado de los items.
Ejercicios para Practicar
Ejercicio 1: App de Contactos
Crea una lista de contactos con búsqueda en tiempo real.
Características: Scroll rápido, búsqueda, agrupación por letra
Ejercicio 2: Feed de Noticias
Implementa un feed infinito que cargue más contenido al hacer scroll.
Características: Scroll infinito, diferentes tipos de items, pull-to-refresh
Ejercicio 3: Carrito de Compras
Crea un carrito de compras con items que se pueden modificar.
Características: Items editables, total dinámico, animaciones
Solución de Problemas Comunes
❌ Error: "Vertical viewport was given unbounded height"
Solución: Envuelve el ListView en un Expanded o usa shrinkWrap: true
// En un Column
Expanded(
child: ListView.builder(...)
)
// O usar shrinkWrap
ListView.builder(
shrinkWrap: true,
...
)
❌ La lista no se actualiza cuando cambian los datos
Solución: Asegúrate de llamar setState() cuando modifiques la lista
void _addItem() {
setState(() {
items.add(newItem);
});
}
❌ Scroll lento con items complejos
Solución: Optimiza el itemBuilder y usa const widgets
// Usar const widgets
itemBuilder: (context, index) {
return const MyListItemWidget(item: items[index]);
}
// Dividir widgets complejos
itemBuilder: (context, index) {
return ComplexItemWidget(item: items[index]);
}