跳到内容
CrossFly 是 100% 开源在 GitHub 关注我们
CrossFlyCrossFly
开始使用

本页面由葡萄牙语机器翻译,可能存在错误。葡萄牙语版本为准。

PagedDataAdapter

PagedDataAdapter 是面向 VirtualListView 的分页 DataAdapter,带页面 LRU 缓存。它只在内存中保留若干页面;当滚动到某个页面已被丢弃的区域时,会显示加载指示器并通过 loadPage 异步重新加载。

它是一个抽象类:你需要实现 getTotalCount、loadPage、createView 和 bindItem。索引→页面的转换、缓存、加载指示器以及加载期间的滚动锁定都已现成提供。

🔴 · 包: dev.crossfly.ui · 实现: VirtualListView.DataAdapter · 用法: 传给 VirtualListView 的构造函数

何时使用

  • 数据来自网络/数据库并按页获取,无法全部放入内存。
  • 你希望自动缓存(LRU),并在回到旧区域时透明地重新加载。

何时不要使用

完整示例(复制、粘贴并运行)

package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.graphics.*;   // Relative

/** 可执行的主界面。将 launcher 指向此类(classname)并运行: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 项,共 5 页
         public int getTotalCount() { return total; }
         public void loadPage(int pageIndex, PageCallback cb) {
            // 模拟一次加载(请替换为后台的网络/数据库调用)。
            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);   // 列表使用 LAY_FILL(占满高度)
   }
}

下面的片段只展示核心部分(放在 onShow() 里的内容)。要运行它们,请放入上面的 MainWindow 骨架中。

基本用法

PagedDataAdapter adapter = new PagedDataAdapter(20, 5) { // 每页 20 项,内存中缓存 5 页
   public int getTotalCount() { return serverTotal; }
   public void loadPage(int pageIndex, PageCallback cb) {
      api.fetch(pageIndex, data -> 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);
layout.add(LAY_FILL, list);

构造函数

构造函数 描述
PagedDataAdapter(int pageSize, int maxCachedPages) pageSize:每页项数。maxCachedPages:内存中保留的页面数(LRU)

这两个字段都是 public final。当你把此 adapter 传给 VirtualListView 的构造函数时,VirtualListView 会将自身注入到该 adapter 中。

需要实现的抽象方法

方法 描述
int getTotalCount() 服务器上的项总数(由 getCount() 使用)
void loadPage(int pageIndex, PageCallback callback) 异步加载一页;完成时调用 callback.onPageLoaded(pageIndex, items)(可在任意线程)
Component createView() throws Exception 创建一个空白视图
void bindItem(Component view, Object item, int index) throws Exception 将原始项(Object)绑定到视图 —— 请转换为你的类型

有用的属性与方法

方法 类型 作用
isAvailable(int index) boolean 该项是否已在缓存中
clearCache() void 清空缓存和加载状态
getCount() int 委托给 getTotalCount()

工作原理(概要)

  • bindView(view, index) 转换出 pageIndex = index / pageSize 和 itemInPage = index % pageSize。
    • 页面在缓存中 → 立即 bindItem(waitingForData = false)。
    • 页面缺失 → waitingForData = true 并触发 requestPage(pageIndex)。
  • requestPage 避免重复请求(loading),显示加载指示器并暂停 fling;回调返回时,用 Component.onUI(...) 编组到 UI 线程,写入页面,隐藏加载指示器,强制重新绑定该范围(invalidateRange)并恢复 fling。
  • cache 是一个处于访问顺序(access-order)模式的 LinkedHashMap,当超过 maxCachedPages 时丢弃最旧的页面(LRU)。

回调与事件

回调 触发时机
PageCallback.onPageLoaded(int pageIndex, Object[] items) 你在完成页面加载时调用(可在任意线程)

状态

  • waitingForData —— 只要有页面在加载就为 true;向 VirtualListView 发出信号以锁定滚动。
  • LRU —— 旧页面被丢弃;滚动回去时会重新加载(新的加载指示器 + 请求)。

示例 —— 类型化项的转换

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;            // 对原始 Object 进行 cast
      ((Label) view).setText(c.getNome());
   }
};
VirtualListView list = new VirtualListView(Relative.ofFont(2.4f), adapter);

最佳实践

  • 页面中的项以 Object[] 到达 —— 在 bindItem 中将 item 转换为你的类型。
  • 在回调之前不要在 loadPage 内调用 UI API —— adapter 在收到 onPageLoaded 时已在内部切换到 UI 线程。
  • 根据滚动模式调整 maxCachedPages:更大的缓存可避免回滚时重新加载,代价是更多内存。

常见错误

  • 用错误的 pageIndex 调用 onPageLoaded —— adapter 用该索引写入缓存并使正确的范围失效。
  • 在 loadPage 中阻塞 —— 它必须是异步的;UI 线程不能等待网络。

另请参阅

这个页面有帮助吗?