# Como implementar carregar-mais (scroll infinito)

> Anexar mais itens automaticamente quando o usuário chega perto do fim da lista virtual.

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

# Como implementar carregar-mais (scroll infinito)

**Propósito.** Carregar mais dados automaticamente à medida que o usuário se aproxima do fim da
lista, anexando itens ao dataset existente (append). Diferente da paginação com cache LRU
([paginacao.md](/docs/cookbook/lists/paginacao)), aqui você controla o dataset e a lista apenas avisa quando é hora
de buscar mais.

## APIs usadas
- `VirtualListView.onLoadMore(Runnable callback)` — dispara quando o scroll chega perto do fim. Só um disparo por vez fica pendente (`loadMorePending`), evitando chamadas duplicadas.
- `VirtualListView.loadMoreThreshold(int threshold)` — quantos itens **antes do fim** disparam o callback. Default `5`.
- `VirtualListView.notifyDataChanged()` — **obrigatório** chamar depois de anexar os novos itens; ele limpa o `loadMorePending`, esconde o spinner e re-mede o conteúdo.

## Exemplo completo (copiar, colar e rodar)
```java
package com.exemplo;

import java.util.*;   // List / ArrayList

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.graphics.*;   // Relative (para VirtualListView)

/** Tela principal executável. Aponte o launcher (classname) e rode: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   final List<String> dados = new ArrayList<>();
   int proxima = 0;

   /** Anexa uma leva de 30 itens ao dataset (simula uma página do servidor). */
   private void carregarPagina()
   {
      for (int i = 0; i < 30; i++)
         dados.add("Registro " + (proxima++));
   }

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

      carregarPagina();   // popula a primeira leva

      VirtualListView lista = new VirtualListView(Relative.ofFont(3f), new VirtualListView.DataAdapter()
      {
         public int getCount()                    { return dados.size(); }
         public Component createView()            { return new Label(""); }
         public void bindView(Component v, int i) throws Exception
         {
            ((Label) v).setText(dados.get(i));
         }
      });

      lista.loadMoreThreshold(8);   // começa a buscar 8 itens antes do fim (default é 5)
      lista.onLoadMore(() ->
      {
         // dispara UMA vez ao aproximar do fim; próximos disparos só após notifyDataChanged
         onUI(() ->
         {
            carregarPagina();             // ANEXA (novos índices no fim)
            lista.notifyDataChanged();    // libera o próximo load-more e esconde o spinner
         });
      });

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

> O trecho abaixo mostra **só o miolo**. Para rodar, coloque-o no esqueleto `MainWindow` acima.

## Trecho mínimo
```java
// verificado na fonte: VirtualListView.java (onLoadMore / loadMoreThreshold / notifyDataChanged)
final Array<String> dados = new Array<>();
carregarPagina(dados, 0);   // popula a primeira leva

VirtualListView lista = new VirtualListView(Relative.ofFont(3f), new VirtualListView.DataAdapter()
{
   public int getCount()                    { return dados.size(); }
   public Component createView()            { return new Label(""); }
   public void bindView(Component v, int i) throws Exception
   {
      ((Label) v).setText(dados.get(i));
   }
});

lista.loadMoreThreshold(8);   // começa a buscar 8 itens antes do fim (default é 5)
lista.onLoadMore(() ->
{
   // dispara UMA vez ao aproximar do fim; próximos disparos só após notifyDataChanged
   api.proximaPagina(novos ->
   {
      dados.addAll(novos);          // ANEXA (novos índices no fim)
      lista.notifyDataChanged();    // libera o próximo load-more e esconde o spinner
   });
});

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

## Aprofunde-se
- O `VirtualListView` mostra automaticamente um **spinner no rodapé** enquanto o load-more está pendente (controlado pelo `VirtualListViewStyle.showSpinner`).
- Se você **não** chamar `notifyDataChanged()`, `loadMorePending` fica travado em `true` e o callback nunca mais dispara.
- Precisa de páginas descartáveis com cache limitado (em vez de append infinito)? Use [paginacao.md](/docs/cookbook/lists/paginacao).
- Guia de widget: [VirtualListView](/docs/ui/widgets/virtuallistview) · Receita irmã: [lista-virtual-grande.md](/docs/cookbook/lists/lista-virtual-grande) · Fonte canônica: Capítulo 15
