# 如何在虚拟列表中对服务器数据进行分页

> 在 VirtualListView 之上使用 PagedDataAdapter（LRU 缓存）按需加载页面。

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

# 如何在虚拟列表中对服务器数据进行分页

**目的。** 显示一个源为分页（例如：REST API）的大型数据集，而无需一次性加载全部内容。
`PagedDataAdapter` 维护一个**页面的 LRU 缓存**：内存中只保留有限数量的页面；当滚动到
数据已被丢弃的区域时，会显示加载指示器并通过 `loadPage` 重新加载该页面。
在页面加载期间滚动会被锁定。

## 使用的 API
- `abstract class PagedDataAdapter(int pageSize, int maxCachedPages)` — 每页 `pageSize` 项；内存中最多 `maxCachedPages` 页（LRU）。实现 `VirtualListView.DataAdapter`。
- `abstract int getTotalCount()` — 服务器上的项目总数（成为列表的 `getCount()`）。
- `abstract void loadPage(int pageIndex, PageCallback callback)` — **异步**加载一个页面；完成后调用 `callback.onPageLoaded(pageIndex, items)`。可从任何线程调用——列表会切换到 UI 线程。
- `abstract Component createView() throws Exception` — 创建可重用的空白视图。
- `abstract void bindItem(Component view, Object item, int index) throws Exception` — 用项目的**对象**填充视图（而非文本）。与原始的 `bindView` 不同：这里你接收的是页面已解析好的项。
- `interface PageCallback { void onPageLoaded(int pageIndex, Object[] items); }`
- `PagedDataAdapter.isAvailable(int index)` — 如果索引已在缓存中则为 true。
- `PagedDataAdapter.clearCache()` — 清除缓存和加载状态。

## 完整示例（复制、粘贴并运行）
```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
{
   /** 模拟的业务项 — 替换为你真实的实体。 */
   static class Registro
   {
      final String nome;
      Registro(String nome) { this.nome = nome; }
      String getNome() { return nome; }
   }

   static final int totalNoServidor = 12345;   // 例如：API 的 metadata

   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      PagedDataAdapter adapter = new PagedDataAdapter(20, 5)   // 每页 20 项，内存中缓存 5 页
      {
         public int getTotalCount()
         {
            return totalNoServidor;
         }

         public void loadPage(int pageIndex, PageCallback callback)
         {
            // 异步：这里在后台模拟服务器响应
            onUI(() ->
            {
               Object[] registros = new Object[20];
               for (int i = 0; i < registros.length; i++)
                  registros[i] = new Registro("Item " + (pageIndex * 20 + i));
               callback.onPageLoaded(pageIndex, registros);
            });
         }

         public Component createView() throws Exception
         {
            return new Label("");                  // 可复用的简单行
         }

         public void bindItem(Component view, Object item, int index) throws Exception
         {
            Registro r = (Registro) item;          // 该页中已解析好的项
            ((Label) view).setText(r.getNome());
         }
      };

      // PagedDataAdapter 是一个 DataAdapter：直接传给 VirtualListView
      VirtualListView lista = new VirtualListView(Relative.ofFont(3f), adapter);
      add(LAY_FILL, lista);
   }
}
```

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

## 最小片段
```java
// 已在源码核实：PagedDataAdapter.java
PagedDataAdapter adapter = new PagedDataAdapter(20, 5)   // 每页 20 项，内存中缓存 5 页
{
   public int getTotalCount()
   {
      return totalNoServidor;                // 例如：12345（来自 API 的 metadata）
   }

   public void loadPage(int pageIndex, PageCallback callback)
   {
      // 异步：发起请求并通过回调响应
      api.buscarPagina(pageIndex, 20, registros ->
         callback.onPageLoaded(pageIndex, registros));   // registros = Object[]
   }

   public Component createView() throws Exception
   {
      return new Label("");                  // 可复用的简单行
   }

   public void bindItem(Component view, Object item, int index) throws Exception
   {
      Registro r = (Registro) item;          // 该页中已解析好的项
      ((Label) view).setText(r.getNome());
   }
};

// PagedDataAdapter 是一个 DataAdapter：直接传给 VirtualListView
VirtualListView lista = new VirtualListView(Relative.ofFont(3f), adapter);
layout.add(1f, lista);
```

## 深入了解
- `VirtualListView` 在构造函数中自动与 adapter 绑定（`adapter.listView = this`）——无需手动关联。
- 当 `loadPage` 正在进行时，adapter 会标记 `waitingForData`，`VirtualListView` 会**阻止 pan/fling/wheel** 并暂停当前的 fling（页面到达时恢复）。
- 通过追加实现无限滚动（无 LRU 缓存），参见 [carregar-mais.md](/docs/cookbook/lists/carregar-mais)。
- 组件指南：[PagedDataAdapter](/docs/ui/widgets/paged-data-adapter) · 最佳实践：[性能与大型列表](/docs/best-practices/performance-e-listas-grandes) · 权威来源：第 15 章
