Esta página fue traducida automáticamente del portugués y puede contener errores. La versión en portugués es la canónica.
VirtualListView
VirtualListView es una lista virtualizada: crea y renderiza solamente los elementos visibles en la pantalla y recicla los componentes que salen del viewport, al estilo del RecyclerView de Android. Soporta miles (o millones) de elementos con apenas unas pocas decenas de componentes vivos, manteniendo el uso de memoria constante.
La fuente de datos es la interfaz DataAdapter (getCount / createView / bindView). La altura de cada elemento es fija (Relative itemHeight). Incluye callback de “cargar más” (onLoadMore) y spinner de carga.
🔴 · Paquete:
dev.crossfly.ui· Extiende:Component· Estilo:VirtualListViewStyle(víagetSkin().virtualListView())
Cuándo usar
- Listas grandes (cientos de miles de filas) donde crear un componente por elemento es inviable.
- Desplazamiento infinito con carga incremental (
onLoadMore). - Paginación remota con caché → combina con
PagedDataAdapter.
Cuándo NO usar
- Listas pequeñas de texto →
ListViewes más simple y mide la altura exacta. - Alturas de elemento realmente variables — la altura aquí es fija (elige un slot que albergue el elemento más grande).
Apariencia
Columna vertical desplazable de elementos de altura uniforme. Durante el desplazamiento, los elementos fuera de pantalla se recogen en un pool y se reaprovechan. Un spinner opcional aparece en el pie (load-more) o en el centro (carga de página).
Ejemplo completo (copiar, pegar y ejecutar)
package com.exemplo;
import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.graphics.*; // Relative
import java.util.*; // List, ArrayList
/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
@Override
public void onShow() throws Exception
{
super.onShow();
setLayout(new VerticalLayout());
final List<String> items = new ArrayList<>();
for (int i = 0; i < 10_000; i++)
items.add("Item " + i);
VirtualListView list = new VirtualListView(Relative.ofFont(2f),
new VirtualListView.DataAdapter() {
public int getCount() { return items.size(); }
public Component createView() { return new Label(""); }
public void bindView(Component v, int index) {
((Label) v).setText(items.get(index));
}
});
add(LAY_FILL, list); // las listas usan LAY_FILL (absorben la altura)
}
}
Los fragmentos siguientes muestran solo el núcleo (lo que va dentro de
onShow()). Para ejecutarlos, colócalos en el esqueletoMainWindowde arriba.
Uso básico
VirtualListView list = new VirtualListView(Relative.ofFont(2f),
new VirtualListView.DataAdapter() {
public int getCount() { return items.size; }
public Component createView() { return new Label(""); }
public void bindView(Component v, int index) {
((Label) v).setText("Item " + index);
}
});
layout.add(LAY_FILL, list);
Constructores
| Constructor | Descripción |
|---|---|
VirtualListView(Relative itemHeight, DataAdapter adapter) |
Único constructor. itemHeight es la altura fija de cada elemento; adapter es la fuente de datos |
No existe constructor sin argumentos. Si
adapteresnull, el constructor llama acreateAdapter()(que sobrescribes en una subclase). Si el adapter es unPagedDataAdapter, el constructor le inyectathisautomáticamente.
DataAdapter
public interface DataAdapter {
int getCount(); // total de ítems
Component createView() throws Exception; // crea view en blanco (solo cuando el pool está vacío)
void bindView(Component view, int index) throws Exception; // vincula datos del índice a la view
}
getCount()informa el total — la lista finge una alturacount * itemHeightsin materializar todos los elementos.createView()solo se llama cuando el pool de reciclaje está vacío.bindView()se ejecuta muchas veces durante el desplazamiento: mantenlo idempotente y barato.
Propiedades y personalización
| Propiedad / método | Tipo | Predeterminado | Qué hace |
|---|---|---|---|
onLoadMore(Runnable) |
VirtualListView |
null |
Callback disparado al desplazarse cerca del final |
loadMoreThreshold(int) |
VirtualListView |
5 |
Cuántos elementos antes del final disparan el load-more (graba en vlvStyle()) |
notifyDataChanged() |
void |
— | Llámalo tras agregar datos (nueva página); re-clampa el scroll y re-vincula el rango |
reloadData() |
void |
— | Llámalo tras alterar el contenido bajo los mismos índices (búsqueda/filtro/sort); limpia caché, desplaza al inicio, re-vincula todo |
scrollToIndex(int) |
void |
— | Centra el elemento en el viewport |
scrollToY(float) |
void |
— | Desplaza a un offset en píxeles (sub-píxel/animable) |
getScrollY() |
float |
— | Offset vertical actual (útil para header colapsable) |
setItemHeight(Relative) |
void |
— | Cambia la altura del elemento y llama a notifyDataChanged() |
getItemHeight() |
int |
— | Altura del elemento en px |
replaceView(int, Component) |
void |
— | Sustituye la view activa de un índice por un componente totalmente nuevo |
Callbacks y eventos
| Callback | Cuándo se dispara |
|---|---|
onLoadMore(Runnable) |
Cuando lastVisible >= count - threshold y no hay carga pendiente |
Los eventos de scroll (onPan/onFling/onMouseWheel) son bloqueados mientras el adapter paginado espera datos (isWaitingForData()), evitando desplazarse al vacío.
Estados
- Reciclando — los elementos fuera del viewport vuelven al pool; al reingresar, son re-vinculados vía
bindView. - Cargando (load-more) — spinner en el pie,
loadMorePending = truehastanotifyDataChanged(). - Esperando página (con
PagedDataAdapter) — scroll trabado hasta que llegue la página.
Lista de 10.000 elementos con load-more
VirtualListView list = new VirtualListView(Relative.ofFont(2f),
new VirtualListView.DataAdapter() {
public int getCount() { return items.size; }
public Component createView() { return new Label(""); }
public void bindView(Component v, int index) {
((Label) v).setText("Item " + index);
}
});
list.onLoadMore(() -> {
carregarProximaPagina(); // agrega ítems a la fuente
list.notifyDataChanged(); // avisa a la lista
});
layout.add(LAY_FILL, list);
Cambio dinámico de adapter vía subclase
No hay setAdapter público en la clase base (el campo adapter es protected). Para cambiar de adapter en tiempo de ejecución, haz una subclase:
class RowList extends VirtualListView {
RowList() { super(Relative.ofFont(2f), null); }
void setAdapter(DataAdapter a, Relative h) { adapter = a; setItemHeight(h); }
}
RowList vsc = new RowList();
final int count = 2000;
vsc.setAdapter(new VirtualListView.DataAdapter() {
public int getCount() { return count; }
public Component createView() { return new Label(""); }
public void bindView(Component v, int index) {
((Label) v).setText("Row id: " + index);
}
}, Relative.ofPixels(100));
Buenas prácticas
- La altura de elemento es fija — elige un slot que albergue el elemento más grande.
- Tras agregar datos, llama a
notifyDataChanged(); tras alterar el contenido bajo los mismos índices, llama areloadData()(de lo contrario las filas ya visibles quedan con datos viejos). - Si
bindViewcrea un componente nuevo por índice en vez de mutar el reciclado, usareplaceView(...)— o acepta que esas views no vuelven al pool.
Errores comunes
- Asignar recursos pesados en
bindView— se ejecuta en cada evento de scroll. Haz un binding barato. - Olvidar
notifyDataChanged()tras agregar página — la nueva altura de contenido no se reconoce y el load-more no se resetea. - Usar
reloadData()cuando solo agregaste — desplaza al inicio; para append usanotifyDataChanged().
Ver también
- Guía del área:
ui/· Recetas:cookbook/ - Componentes relacionados:
PagedDataAdapter·ListView