JavaFX ListView滚动导致大量数据访问

时间:2018-05-01 18:15:39

标签: listview javafx

在使用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);
    }

}

1 个答案:

答案 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;表示单元格已请求加载项目,但加载不完整。