# 加载数据

> 在绘制线程之外做 I/O(Thread + onUI),用 VirtualListView 高效处理大型列表。

Source: https://crossfly.dev/zh/docs/learn/loading-data — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 8. 加载数据

> **你将学到:** 在不卡住 UI 的情况下获取数据,并高效展示大型列表。**前置条件:** [管理状态](/docs/learn/managing-state)。

## 完整示例(复制、粘贴、运行)
在后台 `Thread` 中加载数据,用 `Component.onUI` 回到绘制线程,并在一个会回收视图的虚拟化列表(`VirtualListView` + `DataAdapter`)中展示。可直接编译运行。
```java
package com.exemplo;

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

public class MainWindow extends Window
{
   private final List<Tarefa> tarefas = new ArrayList<>();
   private VirtualListView lista;

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

      lista = new VirtualListView(Relative.ofFont(3f), new DataAdapter()
      {
         public int getCount() { return tarefas.size(); }
         public Component createView() { return new Label(""); }               // 创建一次
         public void bindView(Component v, int i) { ((Label) v).setText(tarefas.get(i).titulo); }
      });
      add(LAY_FILL, lista);

      carregar();
   }

   private void carregar()
   {
      new Thread(() -> {
         var dados = listarDoRepositorio();          // 繁重工作，后台 thread
         Component.onUI(() -> {                       // 返回 paint thread
            tarefas.clear();
            tarefas.addAll(dados);
            lista.notifyDataChanged();                // 更新虚拟化列表
         });
      }).start();
   }

   private List<Tarefa> listarDoRepositorio()        // 模拟 I/O
   {
      List<Tarefa> out = new ArrayList<>();
      for (int i = 1; i <= 500; i++) out.add(new Tarefa("Tarefa " + i));
      return out;
   }
}

class Tarefa
{
   final String titulo;
   Tarefa(String titulo) { this.titulo = titulo; }
}
```
> 下面的片段只展示**核心部分**(放在 `onShow()` 里的内容)。要运行,请把它们放进上面的 `MainWindow` 骨架里。

## 为什么 I/O 从不在绘制线程上进行
UI 只在一个线程上运行 —— 其中任何阻塞都会**冻结界面**。必须遵循的模式:
```java
new Thread(() -> {
   var dados = repo.listar();                          // 繁重工作，后台 thread
   Component.onUI(() -> {                               // 返回 paint thread
      vm.tarefas = dados;
      lista.notifyDataChanged();                        // 更新虚拟化列表
   });
}).start();
```
若要对一个已在后台的 UI 事件做出反应,请使用 `onActionThread`(见[处理输入](/docs/learn/handling-input))。

## 大型列表:`VirtualListView` + `DataAdapter`
`VirtualListView` **回收视图** —— 只渲染可见部分。adapter 将**创建**(一次)与**绑定**(廉价,多次)分离开来:
```java
VirtualListView lista = new VirtualListView(Relative.ofFont(3f), new DataAdapter() {
   public int getCount() { return vm.tarefas.size(); }
   public Component createView() throws Exception { return new CardTarefa(); }
   public void bindView(Component v, int i) throws Exception { ((CardTarefa) v).bind(vm.tarefas.get(i)); }
});
add(LAY_FILL, lista);
// 当数据变化时：
lista.notifyDataChanged();
```
> `bindView` 在滚动时会运行**非常多**次 —— 保持它廉价且幂等(切勿在其中做 I/O)。

## 本步骤所构建的
列表转为在**后台加载**并进行**虚拟化渲染**(使用第 3 章的 `CardTarefa`)。

## 回顾
I/O 在 `Thread` 中 → `Component.onUI`;大型列表用 `VirtualListView`(通过 `createView`/`bindView` 回收);数据变化时调用 `notifyDataChanged()`。

## 下一步
→ [持久化数据](/docs/learn/persisting-data)

## 另见
- [性能与大型列表](/docs/best-practices/performance-e-listas-grandes) · [后台工作](/docs/data-and-services/background-work)
