# 性能与大列表

> 在数据量大时保持 60 fps——虚拟化（VirtualListView）、分页以及低开销的 onPaint。

Source: https://crossfly.dev/zh/docs/best-practices/performance-e-listas-grandes — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 性能与大列表

如何保持 60 fps——即使在低端 GPU 上——同时面对大量数据或繁重的绘制。其中的"为什么"（单一绘制线程、按需渲染）请见 [绘制线程](/docs/concepts/the-paint-thread) 和 [渲染管线](/docs/concepts/rendering-pipeline)。

## 虚拟化：用 `VirtualListView`，而非 `ListView`
`ListView` 会把**所有**项目保留在内存中。对于成百上千项，请使用 `VirtualListView`——它会**回收视图**（只渲染可见部分）。适配器将**创建**（一次性，被回收复用）与**绑定**（开销小，运行很频繁）分离开来：
```java
VirtualListView lista = new VirtualListView(Relative.ofFont(3f), new DataAdapter() {
   public int getCount() { return dados.size(); }
   public Component createView() throws Exception {         // 很少创建（pool）
      return new Label("");
   }
   public void bindView(Component v, int i) throws Exception {  // 滚动时频繁运行 → 廉价且幂等
      ((Label) v).setText(dados.get(i).nome);
   }
});
// 数据变化时：
lista.notifyDataChanged();
```
> `bindView` 必须**开销小且幂等**——它在滚动时每次回收都会运行。那里不要做任何 I/O、分配或昂贵的计算。

**为什么要回收：** 真正的成本在于 `createView()`。通过回收，内存保持**恒定**，与 `getCount()` 无关——10 项还是 10,000 项，都只用同一小把视图。

## 远程分页：`PagedDataAdapter`
对于按页（网络）到来的数据，使用 `PagedDataAdapter`（内部有 **LRU** 缓存）：
```java
var adapter = new PagedDataAdapter(50, 4) {   // 每页 50 项，缓存 4 页
   public int getTotalCount() { return total; }
   public void loadPage(int page, PageCallback cb) {
      new Thread(() -> {
         Object[] itens = api.buscarPagina(page, pageSize);   // 不在 paint thread 中
         Component.onUI(() -> cb.onPageLoaded(page, itens));   // 回到 UI
      }).start();
   }
   public Component createView() throws Exception { return new Label(""); }
   public void bindItem(Component v, Object item, int i) throws Exception {
      ((Label) v).setText(((Cliente) item).nome);
   }
};
```

## 绘制：低开销的 `onPaint` 与按需渲染
- **不要每帧重绘。** 只在**状态变化时**调用 `repaint()`——屏幕不会自行重绘（按需渲染）。
- **不要在 `onPaint` 中分配对象。** 它会为整棵树在每一帧运行；把对象（`GRect`、`Font`）复用在字段里，不要每帧新建。
- **绝不要阻塞绘制线程**——数据库/网络 I/O 放在工作线程 + `onUI`。

## 最佳实践
- 从约数百项起用 `VirtualListView`；`ListView` 只用于小型/固定列表。
- `bindView`/`bindItem` 保持精简；在外部加载图片/数据，再绑定已就绪的内容。
- 大图片：一次性缩放（破坏性缩放），而不是每帧缩放。

## 常见错误
- 用 `ListView` 装载数千项（内存溢出/卡死）。
- `bindView` 做 I/O 或分配（导致滚动卡顿）。
- 循环/逐帧重绘，而不是按需重绘。

## 另请参阅
- [VirtualListView](/docs/ui/widgets/virtuallistview) · [PagedDataAdapter](/docs/ui/widgets/paged-data-adapter)
