Просто отметим, что если вы загружаете изображения из отдельных файлов, а не из почтового индекса файла, существует механизм, который избавляет от необходимости работать непосредственно с любым видом резьбы в всего:
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);
}
}
});
к сожалению, если вы загружаете из архива, я не думаю, что вы можете использовать это, так что вам придется создавать свои собственные фоновые задачи. Вам нужно просто быть осторожным, чтобы убедиться, что вы не используете изображение, загруженное в фоновом режиме, если элемент в ячейке изменяется во время процесса загрузки (что весьма вероятно, если пользователь прокручивает много).
(Update. Примечание: Я изменил это слушать изменения в 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 ;
});
Некоторые мысли о производительности здесь:
Во-первых, «виртуализировать d "механизма ComboBox
означает, что будет создано только небольшое количество этих ячеек, поэтому вам не нужно беспокоиться о том, что вы сразу начинаете тысячи потоков, загружающих изображения, или действительно, что у вас когда-либо будет тысячи изображений в памяти.
Когда пользователь прокручивает список, itemProperty(...)
может изменяться довольно часто, поскольку ячейки повторно используются для новых элементов. Важно убедиться, что вы не используете изображения из запущенных потоков, но не заканчиваете, прежде чем элемент снова изменится; это цель отмены существующей задачи в начале прослушивателя изменения позиции. Отмена задачи будет препятствовать вызову обработчика onSucceeded
. Тем не менее, вы по-прежнему будете иметь эти потоки, поэтому, если возможно, реализация вашего call()
метода должна проверять флаг isCancelled()
и прерывать его как можно быстрее, если он возвращает true. Это может быть сложно реализовать: я бы экспериментировал и посмотрел, как это работает с простой реализацией в первую очередь.
Зачем вам нужно это проверить? –
Как я уже сказал, в всплывающем окне ComboBox может быть возможно 2000 значков, и мой план состоял в том, чтобы загружать и отображать те, которые видны в окне просмотра всплывающего окна ComboBox, поскольку пользователь прокручивает всплывающие окна всплывающих окон. – user2499946
Клеточный механизм позаботится об этом (более или менее). Добавлен ответ. –