# How to display a virtual list with thousands of items

> Render huge lists with VirtualListView + DataAdapter recycling views (RecyclerView style).

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

# How to display a virtual list with thousands of items

**Purpose.** Display tens of thousands of items without creating one `Component` per row. The
`VirtualListView` only creates/renders the visible views and **recycles** those that leave the screen (like
Android's `RecyclerView`). All rows have a **fixed height** (`Relative itemHeight`).

## APIs used
- `new VirtualListView(Relative itemHeight, DataAdapter adapter)` — single constructor; the height is **fixed** for all rows.
- `interface DataAdapter` (nested in `VirtualListView`), with three methods:
  - `int getCount()` — total number of items.
  - `Component createView() throws Exception` — creates a **blank** view; called only when the recycling pool is empty.
  - `void bindView(Component view, int index) throws Exception` — populates a view (new or recycled) with the data at the index.
- `VirtualListView.notifyDataChanged()` — call after the dataset grows/changes under **new** indices (e.g., an appended page). It does not rebind rows already visible.
- `VirtualListView.reloadData()` — full reload: discards the view cache, recycles everything, scrolls to the top, and **rebinds** all visible slots. Use in search/filter/sort (content changes under the same indices).
- `VirtualListView.scrollToIndex(int index)` — scrolls to center the index.
- `VirtualListView.getScrollY()` — current vertical offset in pixels (useful for a collapsible header).

## Complete example (copy, paste and run)
```java
package com.exemplo;

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

/** Runnable main screen. Point the launcher (classname) and run: 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 font heights per row (scales with the UI)

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

         public Component createView() throws Exception
         {
            // reusable "empty" view — NO data here
            return new Label("");
         }

         public void bindView(Component view, int index) throws Exception
         {
            // populate the recycled view with the current index's data
            ((Label) view).setText(dados[index]);
         }
      });

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

> The snippet below shows **only the core**. To run it, place it inside the `MainWindow` skeleton above.

## Minimal snippet
```java
// verified in source: VirtualListView.java (DataAdapter is a nested interface)
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 font heights per row (scales with the UI)

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

   public Component createView() throws Exception
   {
      // reusable "empty" view — NO data here
      return new Label("");
   }

   public void bindView(Component view, int index) throws Exception
   {
      // populate the recycled view with the current index's data
      ((Label) view).setText(dados[index]);
   }
});

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

Updating the data:

```java
// Appended more items at the end (new indices) → notifyDataChanged
carregarMaisDados(dados);
lista.notifyDataChanged();

// Applied a filter/search (same index, different content) → reloadData
aplicarFiltro();
lista.reloadData();   // recycles, scrolls back to top and rebinds the visible items
```

## Go deeper
- Critical rule: **never** keep references to views between binds — they are recycled for other indices.
- **Row with several elements** (icon + text + value)? Create a custom `Component` with a
  `bind(...)` method that stores the sub-references as fields: `createView()` returns `new CardTarefa()` and
  `bindView` does `((CardTarefa) view).bind(dados[index])` (see [Thinking in components](/docs/learn/thinking-in-components)).
- Server data by page: see [paginacao.md](/docs/cookbook/lists/paginacao) (uses `PagedDataAdapter`).
- Infinite scroll by append: see [carregar-mais.md](/docs/cookbook/lists/carregar-mais).
- Widget guide: [VirtualListView](/docs/ui/widgets/virtuallistview) · Best practices: [Performance and large lists](/docs/best-practices/performance-e-listas-grandes) · Canonical source: Chapter 15
