# Cómo mostrar una lista virtual con miles de elementos

> Renderizar listas enormes con VirtualListView + DataAdapter reciclando views (estilo RecyclerView).

Source: https://crossfly.dev/es/docs/cookbook/lists/lista-virtual-grande — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Cómo mostrar una lista virtual con miles de elementos

**Propósito.** Mostrar decenas de miles de elementos sin crear un `Component` por fila. El
`VirtualListView` solo crea/renderiza las views visibles y **recicla** las que salen de la pantalla (como el
`RecyclerView` de Android). Todas las filas tienen **altura fija** (`Relative itemHeight`).

## APIs usadas
- `new VirtualListView(Relative itemHeight, DataAdapter adapter)` — constructor único; la altura es **fija** para todas las filas.
- `interface DataAdapter` (anidada en `VirtualListView`), con tres métodos:
  - `int getCount()` — total de elementos.
  - `Component createView() throws Exception` — crea una view **en blanco**; se llama solo cuando el pool de reciclaje está vacío.
  - `void bindView(Component view, int index) throws Exception` — puebla una view (nueva o reciclada) con los datos del índice.
- `VirtualListView.notifyDataChanged()` — llámalo después de que el dataset crezca/cambie bajo **nuevos** índices (ej.: página anexada). No reenlaza filas ya visibles.
- `VirtualListView.reloadData()` — recarga total: descarta la caché de views, recicla todo, desplaza al inicio y **reenlaza** todos los slots visibles. Úsalo en búsqueda/filtro/ordenación (el contenido cambia bajo los mismos índices).
- `VirtualListView.scrollToIndex(int index)` — desplaza hasta centrar el índice.
- `VirtualListView.getScrollY()` — offset vertical actual en píxeles (útil para header colapsable).

## Ejemplo completo (copiar, pegar y ejecutar)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.graphics.*;   // Relative (para VirtualListView)

/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      final String[] dados = new String[50_000];
      for (int i = 0; i < dados.length; i++) dados[i] = "Registro " + i;

      Relative alturaItem = Relative.ofFont(3f);   // ~3 alturas de fuente por fila (escala con la UI)

      VirtualListView lista = new VirtualListView(alturaItem, new VirtualListView.DataAdapter()
      {
         public int getCount()
         {
            return dados.length;
         }

         public Component createView() throws Exception
         {
            // view "vacía" reutilizable — SIN datos aquí
            return new Label("");
         }

         public void bindView(Component view, int index) throws Exception
         {
            // poblar la view reciclada con los datos del índice actual
            ((Label) view).setText(dados[index]);
         }
      });

      add(LAY_FILL, lista);
   }
}
```

> El fragmento de abajo muestra **solo el núcleo**. Para ejecutarlo, colócalo en el esqueleto `MainWindow` de arriba.

## Fragmento mínimo
```java
// verificado en la fuente: VirtualListView.java (DataAdapter es interface anidada)
final String[] dados = new String[50_000];
for (int i = 0; i < dados.length; i++) dados[i] = "Registro " + i;

Relative alturaItem = Relative.ofFont(3f);     // ~3 alturas de fuente por fila (escala con la UI)

VirtualListView lista = new VirtualListView(alturaItem, new VirtualListView.DataAdapter()
{
   public int getCount()
   {
      return dados.length;
   }

   public Component createView() throws Exception
   {
      // view "vacía" reutilizable — SIN datos aquí
      return new Label("");
   }

   public void bindView(Component view, int index) throws Exception
   {
      // poblar la view reciclada con los datos del índice actual
      ((Label) view).setText(dados[index]);
   }
});

layout.add(1f, lista);
```

Actualizando los datos:

```java
// Añadí más ítems al final (nuevos índices) → notifyDataChanged
carregarMaisDados(dados);
lista.notifyDataChanged();

// Apliqué un filtro/búsqueda (mismo índice, contenido distinto) → reloadData
aplicarFiltro();
lista.reloadData();   // recicla, vuelve al tope y revincula lo visible
```

## Profundiza
- Regla crítica: **nunca** guardes referencias a las views entre binds — se reciclan para otros índices.
- **Fila con varios elementos** (ícono + texto + valor)? Crea un `Component` custom con un método
  `bind(...)` que guarde las sub-referencias como campos: `createView()` retorna `new CardTarefa()` y
  `bindView` hace `((CardTarefa) view).bind(dados[index])` (ver [Pensar en componentes](/docs/learn/thinking-in-components)).
- Datos que llegan del servidor por página: mira [paginacao.md](/docs/cookbook/lists/paginacao) (usa `PagedDataAdapter`).
- Scroll infinito por append: mira [carregar-mais.md](/docs/cookbook/lists/carregar-mais).
- Guía del widget: [VirtualListView](/docs/ui/widgets/virtuallistview) · Buenas prácticas: [Rendimiento y listas grandes](/docs/best-practices/performance-e-listas-grandes) · Fuente canónica: Capítulo 15
