PagedDataAdapter
PagedDataAdapter é um DataAdapter paginado para VirtualListView, com um cache LRU de páginas. Mantém só algumas páginas em memória; ao rolar para uma região cuja página foi descartada, mostra um spinner e recarrega assincronamente via loadPage.
É uma classe abstrata: você implementa getTotalCount, loadPage, createView e bindItem. A tradução índice→página, o cache, o spinner e a trava de scroll durante o carregamento já vêm prontos.
🔴 · Pacote:
dev.crossfly.ui· Implementa:VirtualListView.DataAdapter· Uso: passado ao construtor deVirtualListView
Quando usar
- Dados que vêm de rede/banco em páginas e não cabem todos em memória.
- Você quer cache automático (LRU) com recarga transparente ao voltar a uma região antiga.
Quando NÃO usar
- Todos os dados já estão em memória → implemente
DataAdapterdiretamente naVirtualListView. - Lista pequena →
ListView.
Exemplo completo (copiar, colar e rodar)
package com.exemplo;
import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.graphics.*; // Relative
/** Tela principal executável. Aponte o launcher (classname) e rode: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
@Override
public void onShow() throws Exception
{
super.onShow();
setLayout(new VerticalLayout());
final int pageSize = 20;
final int total = 10_000;
PagedDataAdapter adapter = new PagedDataAdapter(pageSize, 5) { // 20 itens/página, 5 páginas
public int getTotalCount() { return total; }
public void loadPage(int pageIndex, PageCallback cb) {
// Simula um carregamento (troque por chamada de rede/banco em background).
Object[] data = new Object[pageSize];
for (int i = 0; i < pageSize; i++)
data[i] = "Item " + (pageIndex * pageSize + i);
cb.onPageLoaded(pageIndex, data);
}
public Component createView() { return new Label(""); }
public void bindItem(Component view, Object item, int index) {
((Label) view).setText(item.toString());
}
};
VirtualListView list = new VirtualListView(Relative.ofFont(2f), adapter);
add(LAY_FILL, list); // listas usam LAY_FILL (absorvem a altura)
}
}
Os trechos abaixo mostram só o miolo (o que vai dentro de
onShow()). Para rodar, coloque-os no esqueletoMainWindowacima.
Uso básico
PagedDataAdapter adapter = new PagedDataAdapter(20, 5) { // 20 itens/página, 5 páginas em memória
public int getTotalCount() { return serverTotal; }
public void loadPage(int pageIndex, PageCallback cb) {
api.fetch(pageIndex, data -> cb.onPageLoaded(pageIndex, data)); // pode ser background
}
public Component createView() { return new Label(""); }
public void bindItem(Component view, Object item, int index) {
((Label) view).setText(item.toString());
}
};
VirtualListView list = new VirtualListView(Relative.ofFont(2f), adapter);
layout.add(LAY_FILL, list);
Construtores
| Construtor | Descrição |
|---|---|
PagedDataAdapter(int pageSize, int maxCachedPages) |
pageSize: itens por página. maxCachedPages: páginas mantidas em memória (LRU) |
Ambos os campos são public final. A VirtualListView injeta a si mesma no adapter quando você passa este adapter ao seu construtor.
Métodos abstratos a implementar
| Método | Descrição |
|---|---|
int getTotalCount() |
Total de itens no servidor (usado por getCount()) |
void loadPage(int pageIndex, PageCallback callback) |
Carrega uma página assíncrona; chame callback.onPageLoaded(pageIndex, items) ao terminar (de qualquer thread) |
Component createView() throws Exception |
Cria uma view em branco |
void bindItem(Component view, Object item, int index) throws Exception |
Vincula o item bruto (Object) à view — faça o cast para o seu tipo |
Propriedades e métodos úteis
| Método | Tipo | O que faz |
|---|---|---|
isAvailable(int index) |
boolean |
Se o item já está no cache |
clearCache() |
void |
Zera cache e estado de carregamento |
getCount() |
int |
Delega a getTotalCount() |
Funcionamento (resumo)
bindView(view, index)traduzpageIndex = index / pageSizeeitemInPage = index % pageSize.- Página no cache →
bindItemimediato (waitingForData = false). - Página ausente →
waitingForData = truee dispararequestPage(pageIndex).
- Página no cache →
requestPageevita requisições duplicadas (loading), mostra spinner e pausa o fling; ao voltar o callback, marshala para a thread de UI comComponent.onUI(...), grava a página, esconde o spinner, força re-bind da faixa (invalidateRange) e retoma o fling.- O
cacheé umLinkedHashMapem modo access-order que descarta a página mais antiga (LRU) quando excedemaxCachedPages.
Callbacks e eventos
| Callback | Quando dispara |
|---|---|
PageCallback.onPageLoaded(int pageIndex, Object[] items) |
Você chama ao terminar de carregar a página (de qualquer thread) |
Estados
waitingForData—trueenquanto alguma página carrega; sinaliza àVirtualListViewpara travar o scroll.- LRU — páginas antigas são descartadas; ao rolar de volta, são recarregadas (novo spinner + requisição).
Exemplo — cast de item tipado
PagedDataAdapter adapter = new PagedDataAdapter(50, 4) {
public int getTotalCount() { return repo.total(); }
public void loadPage(int page, PageCallback cb) {
repo.loadAsync(page, 50, lista -> cb.onPageLoaded(page, lista.toArray()));
}
public Component createView() { return new Label(""); }
public void bindItem(Component view, Object item, int index) {
Cliente c = (Cliente) item; // cast do Object bruto
((Label) view).setText(c.getNome());
}
};
VirtualListView list = new VirtualListView(Relative.ofFont(2.4f), adapter);
Boas práticas
- Os itens da página chegam como
Object[]— faça o cast doitempara o seu tipo embindItem. - Não chame APIs de UI dentro de
loadPageantes do callback — o adapter já troca para a thread de UI internamente ao receberonPageLoaded. - Ajuste
maxCachedPagesao padrão de rolagem: cache maior evita recargas ao voltar, ao custo de memória.
Erros comuns
- Chamar
onPageLoadedcompageIndexerrado — o adapter usa esse índice para gravar no cache e invalidar a faixa correta. - Bloquear em
loadPage— deve ser assíncrono; a thread de UI não pode esperar a rede.
Veja também
- Guia da área:
ui/· Receitas:cookbook/ - Componentes relacionados:
VirtualListView