# ListView

> Scrollable, embeddable list of text items, with single or multiple selection.

Source: https://crossfly.dev/docs/ui/widgets/listview — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# ListView

`ListView` is a scrollable list of **text** items for selection, ready to embed in any container. Each item becomes a clickable "card". The selection mode is fixed in the constructor via `ItemType`: `SINGLE` (tap selects and confirms) or `MULTIPLE` (checkboxes + "Confirm" button).

Unlike `VirtualListView`, `ListView` **creates one component per item** (no recycling) — it is ideal for small/medium lists (dozens of items), not for thousands of rows.

> 🟡 · **Package:** `dev.crossfly.ui` · **Extends:** `Component` · **Style:** `ListViewStyle` (items/header/button) + `WindowStyle` (background, via `selectionWindowStyle()`)

## When to use
- An embedded selector inside a screen (options menu, filters, category choice).
- A few dozen text items where exact height without scrolling is useful (`computeIdealHeight`).
- Multi-selection with explicit confirmation (`MULTIPLE`).

## When NOT to use
- Large lists (hundreds/thousands) → use [`VirtualListView`](/docs/ui/widgets/virtuallistview), which recycles components.
- Modal choice popup → use [`ListViewWindow`](/docs/ui/widgets/listviewwindow), which wraps the `ListView` in a `Window`.
- Rich rows with image/several fields → use `itemFactory(...)` or build the list by hand with [list items](/docs/ui/widgets/list-items).

## Appearance
Vertical column of centered text cards, with an optional header (title + back icon) and, in `MULTIPLE`, a "Confirm" button at the bottom. In `SINGLE` each card is a `LabelItem`; in `MULTIPLE`, a `CheckLabelItem` (checkbox + label).

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

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

/** 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());

      ListView list = new ListView(new String[]{"Um", "Dois", "Três"}, ListView.ItemType.SINGLE)
         .showHeader(false);
      list.onConfirm(e -> System.out.println("Escolhido: " + list.getSelectedText()));

      add(LAY_FILL, list);   // lists use LAY_FILL (they absorb the height)
   }
}
```

> The snippets below show **only the core** (what goes inside `onShow()`). To run them, place them in the `MainWindow` skeleton above.

## Basic usage
```java
// SINGLE, no header, automatic height (no scrolling)
ListView list = new ListView(new String[]{"Um", "Dois", "Três"}, ListView.ItemType.SINGLE)
   .showHeader(false);
list.onConfirm(e -> System.out.println("Escolhido: " + list.getSelectedText()));
contents.add(.95f, list);
```

## Constructors
| Constructor | Description |
|---|---|
| `ListView(String[] items, ItemType itemType)` | List without title |
| `ListView(String title, String[] items, ItemType itemType)` | List with title in the header |

`ItemType` (defined in `UIConstants`): `SINGLE` or `MULTIPLE`. The mode is **immutable** after construction.

## Properties and customization
| Property / method | Type | Default | What it does |
|---|---|---|---|
| `addItem(String)` | `ListView` | — | Adds an item (lazy rebuild; ignores `null`) |
| `addItems(String...)` | `ListView` | — | Adds several items (a single invalidation) |
| `showHeader(boolean)` | `ListView` | `true` | Shows/hides the header (title + back) |
| `title(String)` | `ListView` | `null` | Sets the title |
| `confirmText(String)` | `ListView` | `"Confirmar"` | Button text (`MULTIPLE` only) |
| `itemStyle(ComponentStyle)` | `ListView` | theme | Style override for all items |
| `confirmButtonStyle(ComponentStyle)` | `ListView` | theme | Style of the confirm button |
| `headerStyle(LabelStyle)` | `ListView` | theme | Header style |
| `itemFactory(ItemFactory)` | `ListView` | `null` | Custom item factory (receives `text`, `index`) |
| `closeAction(Runnable)` | `ListView` | `null` | Close action (the `ListViewWindow` points it to `this::hide`) |
| `computeIdealHeight(int width)` | `int` | — | Exact height without scrolling, or `-1` if there is an `itemFactory` |

### Selection queries
`getSelectedCount()`, `getSelectedIndex()`, `getSelectedText()`, `getSelectedIndices()`, `getSelectedTexts()`, `isSelected(int)`, `getSelectionState()` (`boolean[]`).

## Callbacks and events
| Callback | When it fires |
|---|---|
| `onConfirm(Component.Action)` | `SINGLE`: on tapping an item. `MULTIPLE`: on tapping "Confirm" |

In `SINGLE`, the tap clears the previous selection, marks the index, fires `onConfirm` and calls `close()`. In `MULTIPLE`, the tap toggles the item's checkbox; confirmation comes from the button.

## States
- **SINGLE** — only one item marked at a time; the tap already confirms and closes.
- **MULTIPLE** — several markable items; state queryable via `getSelectionState()` even before confirming.
- Header and footer button are optional (they depend on `showHeader`/`title` and the `ItemType`).

### MULTIPLE with automatic height
```java
ListView multi = new ListView(new String[]{"Vermelho", "Verde", "Azul"},
      ListView.ItemType.MULTIPLE)
   .showHeader(false)
   .confirmText("Aplicar");
multi.getStyle().autoHeight(Relative.ofFont(9f));
multi.onConfirm(e -> {
   for (String t : multi.getSelectedTexts())
      System.out.println("marcado: " + t);
});
contents.add(.95f, multi);
```

### Custom rows with `itemFactory`
```java
ListView list = new ListView(labels, ListView.ItemType.SINGLE).showHeader(false);
list.itemFactory((text, index) -> {
   Component row = new Component();
   row.style(Styles.component().pressable().ingap(Gap.ofFont(.3f)));
   row.setLayout(new GridLayout(Position.START, Position.CENTER));
   row.add(new Label(text));
   return row;
});
list.onConfirm(e -> System.out.println("idx = " + list.getSelectedIndex()));
contents.add(.95f, list);
```

## Best practices
- Prefer `addItems(...)` over several `addItem(...)` calls — a single layout invalidation.
- Use `computeIdealHeight(width)` when you want a list **without scrolling** with exact height (returns `-1` if there is an `itemFactory`, since custom items are not generically measurable).
- Item rebuilding is lazy (it happens on the next layout), including when the width changes.

## Common mistakes
- **Using `ListView` for thousands of items** — it does not recycle; memory grows linearly. Migrate to [`VirtualListView`](/docs/ui/widgets/virtuallistview).
- **Expecting `onConfirm` in `MULTIPLE` on every tap** — in `MULTIPLE` the callback only fires on the "Confirm" button; query `getSelectionState()` to react to each mark.
- **Assuming there is a constructor without items** — the constructor requires `items` and `ItemType`; start with `new String[]{}` and use `addItems(...)`.

## See also
- Area guide: [`ui/`](/docs/ui) · Recipes: [`cookbook/`](/docs/cookbook)
- Related components: [`ListViewWindow`](/docs/ui/widgets/listviewwindow) · [`VirtualListView`](/docs/ui/widgets/virtuallistview) · [list items (`LabelItem`/`CheckLabelItem`)](/docs/ui/widgets/list-items)
