本页面由葡萄牙语机器翻译,可能存在错误。葡萄牙语版本为准。
性能与大列表
如何保持 60 fps——即使在低端 GPU 上——同时面对大量数据或繁重的绘制。其中的“为什么”(单一绘制线程、按需渲染)请见 绘制线程 和 渲染管线。
虚拟化:用 VirtualListView,而非 ListView
ListView 会把所有项目保留在内存中。对于成百上千项,请使用 VirtualListView——它会回收视图(只渲染可见部分)。适配器将创建(一次性,被回收复用)与绑定(开销小,运行很频繁)分离开来:
VirtualListView lista = new VirtualListView(Relative.ofFont(3f), new DataAdapter() {
public int getCount() { return dados.size(); }
public Component createView() throws Exception { // 很少创建(pool)
return new Label("");
}
public void bindView(Component v, int i) throws Exception { // 滚动时频繁运行 → 廉价且幂等
((Label) v).setText(dados.get(i).nome);
}
});
// 数据变化时:
lista.notifyDataChanged();
bindView必须开销小且幂等——它在滚动时每次回收都会运行。那里不要做任何 I/O、分配或昂贵的计算。
为什么要回收: 真正的成本在于 createView()。通过回收,内存保持恒定,与 getCount() 无关——10 项还是 10,000 项,都只用同一小把视图。
远程分页:PagedDataAdapter
对于按页(网络)到来的数据,使用 PagedDataAdapter(内部有 LRU 缓存):
var adapter = new PagedDataAdapter(50, 4) { // 每页 50 项,缓存 4 页
public int getTotalCount() { return total; }
public void loadPage(int page, PageCallback cb) {
new Thread(() -> {
Object[] itens = api.buscarPagina(page, pageSize); // 不在 paint thread 中
Component.onUI(() -> cb.onPageLoaded(page, itens)); // 回到 UI
}).start();
}
public Component createView() throws Exception { return new Label(""); }
public void bindItem(Component v, Object item, int i) throws Exception {
((Label) v).setText(((Cliente) item).nome);
}
};
绘制:低开销的 onPaint 与按需渲染
- 不要每帧重绘。 只在状态变化时调用
repaint()——屏幕不会自行重绘(按需渲染)。 - 不要在
onPaint中分配对象。 它会为整棵树在每一帧运行;把对象(GRect、Font)复用在字段里,不要每帧新建。 - 绝不要阻塞绘制线程——数据库/网络 I/O 放在工作线程 +
onUI。
最佳实践
- 从约数百项起用
VirtualListView;ListView只用于小型/固定列表。 bindView/bindItem保持精简;在外部加载图片/数据,再绑定已就绪的内容。- 大图片:一次性缩放(破坏性缩放),而不是每帧缩放。
常见错误
- 用
ListView装载数千项(内存溢出/卡死)。 bindView做 I/O 或分配(导致滚动卡顿)。- 循环/逐帧重绘,而不是按需重绘。
另请参阅
这个页面有帮助吗?感谢你的反馈!