在使用JavaFX
ListView
进行测试时,我注意到对基础List
的大量访问,提供了项目。
在大多数用例中,这可能没问题。
在我的情况下,这是一个问题,因为我的List
使用了延迟加载。它只包含有关Element的信息,当元素被访问时(通过get(index))元素得到解析(Lazily),因此我搜索了delay
滚动位置的方法。
创建隐藏ListView
VBar
的工作是什么,添加自定义Vertical ScrollBar
,完全符合我的需要 - delay
(使用PauseTransistion
直到用户停止一些ms。
但这个解决方案是一个讨厌的解决方法...
所以我尝试使用现有的方法来推迟ListView
本身的加载/滚动事件。
ListView<Integer> list = new ListView<Integer>();
list.setOnScroll(new EventHandler<ScrollEvent>() {
@Override
public void handle(ScrollEvent e) {
System.out.println("##### OnScroll: " + e);
}
});
list.setOnScrollTo(new EventHandler<ScrollToEvent<Integer>>() {
@Override
public void handle(ScrollToEvent<Integer> e) {
System.out.println("##### OnScrollTo: " + e);
}
});
list.setOnScrollStarted(new EventHandler<ScrollEvent>() {
@Override
public void handle(ScrollEvent e) {
System.out.println("##### OnScrollStarted: " + e);
}
});
list.setOnScrollFinished(new EventHandler<ScrollEvent>() {
@Override
public void handle(ScrollEvent e) {
System.out.println("##### OnScrollFinished: " + e);
}
});
但是这些事件都没有被触发......
=== UPDATE ===
除了@James_D回答我实施了一个快速而肮脏的LazyLoadObservableList
,它展示了通过ListView
这样做@ James_D的代码只需修改两行:
LazyLoadObservableList<LazyLoadItem<String>> lazyItems = new LazyLoadObservableList<>(items);
list.setItems(lazyItems);
这个包装器的代码是:
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import javafx.beans.InvalidationListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
public class LazyLoadObservableList<E> implements ObservableList<E> {
ObservableList<E> observableList;
public LazyLoadObservableList(ObservableList<E> observableList) {
this.observableList = observableList;
}
@Override
public boolean add(E item) {
return observableList.add(item);
}
@Override
public void add(int index, E item) {
observableList.add(index, item);
}
@Override
public boolean addAll(Collection<? extends E> items) {
return observableList.addAll(items);
}
@Override
public boolean addAll(int index, Collection<? extends E> items) {
return observableList.addAll(index, items);
}
@Override
public void clear() {
observableList.clear();
}
@Override
public boolean contains(Object item) {
return observableList.contains(item);
}
@Override
public boolean containsAll(Collection<?> items) {
return observableList.containsAll(items);
}
@Override
public E get(int index) {
System.out.println("ListView requested #"+index);
return observableList.get(index);
}
@Override
public int indexOf(Object item) {
return observableList.indexOf(item);
}
@Override
public boolean isEmpty() {
return observableList.isEmpty();
}
@Override
public Iterator<E> iterator() {
return observableList.iterator();
}
@Override
public int lastIndexOf(Object item) {
return observableList.lastIndexOf(item);
}
@Override
public ListIterator<E> listIterator() {
return observableList.listIterator();
}
@Override
public ListIterator<E> listIterator(int index) {
return observableList.listIterator(index);
}
@Override
public boolean remove(Object item) {
return observableList.remove(item);
}
@Override
public E remove(int index) {
return observableList.remove(index);
}
@Override
public boolean removeAll(Collection<?> items) {
return observableList.removeAll(items);
}
@Override
public boolean retainAll(Collection<?> items) {
return observableList.retainAll(items);
}
@Override
public E set(int index, E item) {
return observableList.set(index, item);
}
@Override
public int size() {
return observableList.size();
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return observableList.subList(fromIndex, toIndex);
}
@Override
public Object[] toArray() {
return observableList.toArray();
}
@Override
public <T> T[] toArray(T[] array) {
return observableList.toArray(array);
}
@Override
public void addListener(InvalidationListener invalidationListener) {
observableList.addListener(invalidationListener);
}
@Override
public void removeListener(InvalidationListener invalidationListener) {
observableList.removeListener(invalidationListener);
}
@Override
public boolean addAll(E... items) {
return observableList.addAll(items);
}
@Override
public void addListener(ListChangeListener<? super E> listChangeListener) {
observableList.addListener(listChangeListener);
}
@Override
public void remove(int fromIndex, int toIndex) {
observableList.remove(fromIndex, toIndex);
}
@Override
public boolean removeAll(E... items) {
return observableList.removeAll(items);
}
@Override
public void removeListener(ListChangeListener<? super E> listChangeListener) {
observableList.removeListener(listChangeListener);
}
@Override
public boolean retainAll(E... items) {
return observableList.retainAll(items);
}
@Override
public boolean setAll(E... items) {
return observableList.setAll(items);
}
@Override
public boolean setAll(Collection<? extends E> items) {
return observableList.retainAll(items);
}
}
答案 0 :(得分:1)
onScroll
处理程序是相当低级的处理程序,它们响应滚轮鼠标事件(类似,例如用于跟踪板)。我很确定他们不会响应被拖动的滚动条(就低级事件而言,这是某种类型的鼠标拖动事件)。
在这里拦截滚动行为似乎完全是错误的做法。我认为这里最好的方法是通过列表单元格,您可以在其中实现&#34;如果项目未加载,并且我暂时没有被要求提供新项目,请加载项目& #34 ;.
当然,这需要物品本身&#34;知道&#34;如果它加载或不加载。因此,您需要某种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);
}
}
在此演示中,&#34; Waiting&#34;表示尚未要求加载项目的单元格,并且&#34;尚未加载&#34;表示单元格已请求加载项目,但加载不完整。