# 如何显示包含数千项的虚拟列表

> 使用 VirtualListView + DataAdapter 通过回收视图（类似 RecyclerView）渲染超大列表。

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

# 如何显示包含数千项的虚拟列表

**目的。** 显示数万个项目而无需为每一行创建一个 `Component`。
`VirtualListView` 只创建/渲染可见的视图，并**回收**移出屏幕的视图（就像 Android 的
`RecyclerView`）。所有行都具有**固定高度**（`Relative itemHeight`）。

## 使用的 API
- `new VirtualListView(Relative itemHeight, DataAdapter adapter)` — 唯一构造函数；所有行的高度都是**固定的**。
- `interface DataAdapter`（嵌套在 `VirtualListView` 中），包含三个方法：
  - `int getCount()` — 项目总数。
  - `Component createView() throws Exception` — 创建一个**空白**视图；仅在回收池为空时调用。
  - `void bindView(Component view, int index) throws Exception` — 用索引处的数据填充视图（新建或回收的）。
- `VirtualListView.notifyDataChanged()` — 在数据集在**新**索引下增长/变化后调用（例如：追加了一页）。不会重新绑定已可见的行。
- `VirtualListView.reloadData()` — 完全重载：丢弃视图缓存、回收所有内容、滚动到顶部并**重新绑定**所有可见的槽位。用于搜索/过滤/排序（内容在相同索引下发生变化）。
- `VirtualListView.scrollToIndex(int index)` — 滚动至使索引居中。
- `VirtualListView.getScrollY()` — 当前垂直偏移量（以像素为单位，对可折叠头部很有用）。

## 完整示例（复制、粘贴并运行）
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.graphics.*;   // Relative（用于 VirtualListView）

/** 可执行的主界面。将 launcher 指向此类（classname）并运行：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 倍字体高度（随 UI 缩放）

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

         public Component createView() throws Exception
         {
            // 可复用的 "空" view — 这里没有数据
            return new Label("");
         }

         public void bindView(Component view, int index) throws Exception
         {
            // 用当前索引的数据填充回收的 view
            ((Label) view).setText(dados[index]);
         }
      });

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

> 下面的片段只展示**核心部分**。要运行它，请将其放入上面的 `MainWindow` 骨架中。

## 最小片段
```java
// 已在源码核实：VirtualListView.java（DataAdapter 是嵌套接口）
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 倍字体高度（随 UI 缩放）

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

   public Component createView() throws Exception
   {
      // 可复用的 "空" view — 这里没有数据
      return new Label("");
   }

   public void bindView(Component view, int index) throws Exception
   {
      // 用当前索引的数据填充回收的 view
      ((Label) view).setText(dados[index]);
   }
});

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

更新数据：

```java
// 我向末尾追加了更多项（新索引）→ notifyDataChanged
carregarMaisDados(dados);
lista.notifyDataChanged();

// 我应用了筛选/搜索（相同索引，内容不同）→ reloadData
aplicarFiltro();
lista.reloadData();   // 回收，回到顶部并重新绑定可见项
```

## 深入了解
- 关键规则：**绝不要**在多次绑定之间保存对视图的引用——它们会被回收用于其他索引。
- **一行包含多个元素**（图标 + 文本 + 数值）？创建一个自定义 `Component`，带一个 `bind(...)`
  方法，将子引用作为字段保存：`createView()` 返回 `new CardTarefa()`，而
  `bindView` 执行 `((CardTarefa) view).bind(dados[index])`（参见 [以组件方式思考](/docs/learn/thinking-in-components)）。
- 数据按页从服务器获取：参见 [paginacao.md](/docs/cookbook/lists/paginacao)（使用 `PagedDataAdapter`）。
- 通过追加实现无限滚动：参见 [carregar-mais.md](/docs/cookbook/lists/carregar-mais)。
- 组件指南：[VirtualListView](/docs/ui/widgets/virtuallistview) · 最佳实践：[性能与大型列表](/docs/best-practices/performance-e-listas-grandes) · 权威来源：第 15 章
