Обработчики onScroll
являются довольно низкоуровневыми обработчиками, которые реагируют на события мыши с колесом прокрутки (и аналогичные, например, для трековых площадок). Я почти уверен, что они не реагируют на перетаскивание полосы прокрутки (с точки зрения низкоуровневого события, которое является каким-либо событием перетаскивания мышью).
Перехват прокрутки здесь просто кажется неправильным подходом. Я думаю, что лучший подход здесь - через ячейку списка, где вы можете реализовать «Если элемент не загружен, и у меня некоторое время не запрашивался новый элемент, загрузите элемент».
Это, конечно, требует, чтобы сам элемент "знал", загружен он или нет. Так что вам понадобится какая-нибудь LazyLoadItem
оболочка:
import java.util.concurrent.Executor;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.concurrent.Task;
public abstract class LazyLoadItem<T> {
private T value ;
private final T placeholder ;
private final ReadOnlyBooleanWrapper loaded = new ReadOnlyBooleanWrapper();
private boolean loadRequested ;
private final Executor executor ;
public LazyLoadItem(Executor executor, T placeholder) {
this.executor = executor ;
this.placeholder = placeholder ;
}
/**
* Executed on background thread on first request to getValue().
* @return The value.
* @throws Exception
*/
protected abstract T load() throws Exception ;
/**
* Requests to load the value in the background, and returns the value if loaded, or the placeholder otherwise.
* Must be executed on the FX Application Thread.
* @return The value, if loaded, otherwise the placeholder. If not loaded, spawns a task to load the value on a background thread.
*/
public T getValue() {
if (! loadRequested) {
loadRequested = true ;
Task<T> task = new Task<T>() {
@Override
protected T call() throws Exception {
return load();
}
};
task.setOnSucceeded(e -> {
value = task.getValue();
loaded.set(true);
});
// do something more sensible here..
task.setOnFailed(e -> task.getException().printStackTrace());
executor.execute(task);
}
if (isLoaded()) {
return value ;
} else {
return placeholder ;
}
}
/**
* Observable property indicating whether the value has been loaded.
* Must be called from the FX Application Thread.
* @return
*/
public final ReadOnlyBooleanProperty loadedProperty() {
return this.loaded.getReadOnlyProperty();
}
/**
* Whether or not the value has been loaded. Must be called from the FX Application Thread.
* @return
*/
public final boolean isLoaded() {
return this.loadedProperty().get();
}
}
Вот тривиальный пример реализации:
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class MockLazyLoadItem extends LazyLoadItem<String> {
// should not really be here: just for demo... Also limiting threads for demo
private static final Executor EXEC = Executors.newFixedThreadPool(10, runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
private final Random rng = new Random();
private final int id ;
public MockLazyLoadItem(int id) {
super(EXEC, "Not yet loaded...");
this.id = id ;
}
@Override
protected String load() throws Exception {
System.out.println("loading item "+id);
Thread.sleep(rng.nextInt(2000)+1000);
return "Item "+id ;
}
}
Теперь реализация вашей ячейки может делать что-то вроде
import javafx.animation.PauseTransition;
import javafx.scene.control.ListCell;
import javafx.util.Duration;
public class LazyLoadingListCell extends ListCell<LazyLoadItem<String>> {
private PauseTransition pause = new PauseTransition(Duration.millis(1000));
@Override
protected void updateItem(LazyLoadItem<String> lazyItem, boolean empty) {
pause.stop();
super.updateItem(lazyItem, empty);
if (empty) {
setText("");
} else if (lazyItem.isLoaded()) {
setText(lazyItem.getValue());
} else {
pause.setOnFinished(e -> setText(lazyItem.getValue()));
setText("Waiting...");
pause.play();
}
}
}
Вот быстрый тест:
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
public class LazyLoadListViewTest extends Application {
@Override
public void start(Stage primaryStage) {
ListView<LazyLoadItem<String>> list = new ListView<>();
// Create list with extractor, so the list cell gets updated
// when the loadedProperty() changes (i.e. when loading completes)
ObservableList<LazyLoadItem<String>> items = FXCollections.observableArrayList(item -> new Observable[] {item.loadedProperty()});
list.setCellFactory(lv -> new LazyLoadingListCell());
for (int i = 1 ; i <= 1000 ; i++) {
items.add(new MockLazyLoadItem(i));
}
list.setItems(items);
Scene scene = new Scene(list);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
В этой демонстрации «Ожидание» указывает на ячейку, которая еще не просила элемент загрузить, а «Еще не загружен» указывает, что ячейка запросила элемент для загрузки, но загрузка не завершена.