# Cómo implementar cargar-más (scroll infinito)

> Anexar más elementos automáticamente cuando el usuario llega cerca del final de la lista virtual.

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

# Cómo implementar cargar-más (scroll infinito)

**Propósito.** Cargar más datos automáticamente a medida que el usuario se acerca al final de la
lista, anexando elementos al dataset existente (append). A diferencia de la paginación con caché LRU
([paginacao.md](/docs/cookbook/lists/paginacao)), aquí tú controlas el dataset y la lista solo avisa cuándo es hora
de buscar más.

## APIs usadas
- `VirtualListView.onLoadMore(Runnable callback)` — se dispara cuando el scroll llega cerca del final. Solo un disparo a la vez queda pendiente (`loadMorePending`), evitando llamadas duplicadas.
- `VirtualListView.loadMoreThreshold(int threshold)` — cuántos elementos **antes del final** disparan el callback. Default `5`.
- `VirtualListView.notifyDataChanged()` — **obligatorio** llamarlo después de anexar los nuevos elementos; limpia el `loadMorePending`, oculta el spinner y vuelve a medir el contenido.

## Ejemplo completo (copiar, pegar y ejecutar)
```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)

/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   final List<String> dados = new ArrayList<>();
   int proxima = 0;

   /** Añade una tanda de 30 ítems al dataset (simula una página del 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();   // puebla la primera tanda

      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);   // empieza a buscar 8 ítems antes del final (default es 5)
      lista.onLoadMore(() ->
      {
         // dispara UNA vez al acercarse al final; próximos disparos solo tras notifyDataChanged
         onUI(() ->
         {
            carregarPagina();             // AÑADE (nuevos índices al final)
            lista.notifyDataChanged();    // libera el próximo load-more y oculta el spinner
         });
      });

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

> El fragmento de abajo muestra **solo el núcleo**. Para ejecutarlo, colócalo en el esqueleto `MainWindow` de arriba.

## Fragmento mínimo
```java
// verificado en la fuente: VirtualListView.java (onLoadMore / loadMoreThreshold / notifyDataChanged)
final Array<String> dados = new Array<>();
carregarPagina(dados, 0);   // puebla la primera tanda

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);   // empieza a buscar 8 ítems antes del final (default es 5)
lista.onLoadMore(() ->
{
   // dispara UNA vez al acercarse al final; próximos disparos solo tras notifyDataChanged
   api.proximaPagina(novos ->
   {
      dados.addAll(novos);          // AÑADE (nuevos índices al final)
      lista.notifyDataChanged();    // libera el próximo load-more y oculta el spinner
   });
});

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

## Profundiza
- El `VirtualListView` muestra automáticamente un **spinner en el pie** mientras el cargar-más está pendiente (controlado por `VirtualListViewStyle.showSpinner`).
- Si **no** llamas a `notifyDataChanged()`, `loadMorePending` queda trabado en `true` y el callback nunca vuelve a dispararse.
- ¿Necesitas páginas descartables con caché limitada (en lugar de append infinito)? Usa [paginacao.md](/docs/cookbook/lists/paginacao).
- Guía del widget: [VirtualListView](/docs/ui/widgets/virtuallistview) · Receta hermana: [lista-virtual-grande.md](/docs/cookbook/lists/lista-virtual-grande) · Fuente canónica: Capítulo 15
