首先请注意,如果您从单个文件而不是从 zip 文件加载图像,则有一种机制可以完全避免直接使用任何类型的线程:
ComboBox<MyDataType> comboBox = new ComboBox<>();
comboBox.setCellFactory(listView -> new ListCell<MyDataType>() {
private ImageView imageView = new ImageView();
@Override
public void updateItem(MyDataType item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
String imageURL = item.getImageURL();
Image image = new Image(imageURL, true); // true means load in background
imageView.setImage(image);
setGraphic(imageView);
}
}
});
不幸的是,如果您从 zip 文件加载,我认为您不能使用它,因此您需要创建自己的后台任务。如果单元格中的项目在加载过程中发生变化(如果用户滚动很多,这很可能),您需要小心一点,以确保您不使用在后台加载的图像。
(更新说明:我将其更改为监听单元格的itemProperty() 的变化,而不是使用updateItem(...) 方法。updateItem(...) 方法可以比实际显示的项目更频繁地调用单元格更改,因此这种方法避免了不必要的图像“重新加载”。)
类似:
ExecutorService exec = ... ;
ComboBox<MyDataType> comboBox = new ComboBox<>();
comboBox.setCellFactory(listView -> {
ListCell<MyDataType> cell = new ListCell<MyDataType>() ;
ImageView imageView = new ImageView();
ObjectProperty<Task<Image>> loadingTask = new SimpleObjectProperty<>();
cell.emptyProperty().addListener((obs, wasEmpty, isNotEmpty) -> {
if (isNowEmpty) {
cell.setGraphic(null);
cell.setText(null);
} else {
cell.setGraphic(imageView);
}
});
cell.itemProperty().addListener((obs, oldItem, newItem) -> {
if (loadingTask.get() != null &&
loadingTask.get().getState() != Worker.State.SUCCEEDED &&
loadingTask.get().getState() != Worker.State.FAILED) {
loadingTask.get().cancel();
}
loadingTask.set(null) ;
if (newItem != null) {
Task<Image> task = new Task<Image>() {
@Override
public Image call() throws Exception {
Image image = ... ; // retrieve image for item
return image ;
}
};
loadingTask.set(task);
task.setOnSucceeded(event -> imageView.setImage(task.getValue()));
exec.submit(task);
cell.setText(...); // some text from item...
}
});
return cell ;
});
关于性能的一些想法:
首先,ComboBox 的“虚拟化”机制意味着只会创建少量这些单元格,因此您不必担心会立即启动数千个线程加载图像,或者事实上,你将永远有成千上万的图像在记忆中。
当用户滚动列表时,itemProperty(...) 可能会频繁更改,因为单元格会重复用于新项目。确保您不使用已启动但在项目再次更改之前未完成的线程中的图像很重要;这是在项目更改侦听器开始时取消现有任务的目的。取消任务将阻止调用onSucceeded 处理程序。但是,这些线程仍然在运行,所以如果可能,call() 方法的实现应该检查isCancelled() 标志,如果它返回 true,则尽快中止。这可能很难实现:我会先试验一下,看看它是如何与一个简单的实现一起工作的。