JavaFX ListView с сенсорными событиями для прокрутки вверх и вниз

Я хотел бы реализовать прокручиваемый ListView с помощью жестов, как на мобильных телефонах и планшетах, для прокрутки моего списка пальцем вверх и вниз. Но мой текущий список выбирает элемент, как только я нажимаю на него. Как я могу этого добиться? Я не смог найти ни одного примера в учебниках Oracle.

private ObservableList<Document> items = FXCollections.observableArrayList();
@FXML ListView<Document> listView;

{
    ...

    listView.setItems(items);
    listView.getStylesheets().add("style/listview.css");
    listView.setStyle("-fx-background-insets: 0 ;"); // remove 1px border of listview container

    listView.setCellFactory(new Callback<ListView<Document>, ListCell<Document>>() {
        @Override
        public ListCell<Document> call(ListView<Document> listView) {
            return new DocumentArrayAdapter();
        }
    });  

    ...
}

public void loadListView(List<Document> ldoc){
    if (!ldoc.isEmpty()){
        items.addAll(ldoc);
    }
}

person jmhostalet    schedule 23.10.2014    source источник
comment
Пожалуйста, взгляните на мой ответ. Очень простой ответ без расширений или около того.   -  person Amin    schedule 03.09.2016


Ответы (4)


Это то, что я сделал

public class CustomListCell extends ListCell<Document>{

private double lastYposition = 0;

public CustomListCell(){

    setOnMousePressed(new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent event) {
            lastYposition = event.getSceneY();
        }
    });

    setOnMouseDragged(new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent event) {
            double newYposition = event.getSceneY();
            double diff = newYposition - lastYposition;
            lastYposition = newYposition;
            CustomScrollEvent cse = new CustomScrollEvent();
            cse.fireVerticalScroll((int)diff, DocumentArrayAdapter.this, (EventTarget) event.getSource());
        }
    });  

а также

package myproject.utils;

import javafx.event.Event;
import javafx.event.EventTarget;
import javafx.scene.input.ScrollEvent;

public class CustomScrollEvent {

public void fireVerticalScroll(int deltaY, Object source, EventTarget target){

    ScrollEvent newScrollEvent = null;
    newScrollEvent = new ScrollEvent(source,
               target,
               ScrollEvent.SCROLL,
               0,
               0,
               0,
               0,
               false,
               false,
               false,
               false,
               false,
               false,
               0,
               deltaY,
               0,
               0,
               ScrollEvent.HorizontalTextScrollUnits.CHARACTERS,
               0,
               ScrollEvent.VerticalTextScrollUnits.NONE,
               deltaY,
               0,
               null);

        Event.fireEvent(target, newScrollEvent);
}
}

Хотя я реализовал прослушиватель в своем собственном ListCell, я думаю, он также будет работать с реализацией слушателей непосредственно в ListView с помощью listView.setOnMousePressed и listView.setOnMouseDragged.

person jmhostalet    schedule 03.11.2014
comment
Когда я запускаю это, я получаю: Конструктор ScrollEvent (Object, EventTarget, EventType ‹ScrollEvent›, int, int, int, int, boolean, boolean, boolean, boolean, boolean, boolean, int, int, int, int, ScrollEvent. HorizontalTextScrollUnits, int, ScrollEvent.VerticalTextScrollUnits, int, int, null) не определено - person officialhopsof; 14.07.2016
comment
Хорошая работа. Я использовал его в TableView для имитации панорамирования без видимой полосы прокрутки. Установите полосу прокрутки как цель, а таблицу как источник. - person tec; 31.07.2021

Вот еще один подход:

public class ScrollListener {

    private BooleanProperty scrolling = new ReadOnlyBooleanWrapper(false);

    private EventHandler<? super MouseEvent> dragDetectedFilter = e -> scrolling.set(true);

    private EventHandler<? super MouseEvent> mouseClickedFilter = evt -> {
        if (scrolling.get()) {
            scrolling.set(false);
            evt.consume();
        }
    };

    private EventHandler<? super MouseEvent> mouseExitedHandler = e -> scrolling.set(false);

    private Node observableNode;

    public ScrollListener(Node observableNode) {
        this.observableNode = observableNode;
    }

    public void enable() {
        observableNode.addEventFilter(MouseEvent.DRAG_DETECTED, dragDetectedFilter);
        observableNode.addEventFilter(MouseEvent.MOUSE_CLICKED, mouseClickedFilter);
        observableNode.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler);
    }

    public void disable() {
        observableNode.removeEventFilter(MouseEvent.DRAG_DETECTED, dragDetectedFilter);
        observableNode.removeEventFilter(MouseEvent.MOUSE_CLICKED, mouseClickedFilter);
        observableNode.removeEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler);
    }

    public ReadOnlyBooleanProperty scrollingProperty() {
        return scrolling;
    }

    public boolean isScrolling() {
        return scrolling.get();
    }
}

Когда вы добавляете слушателя в ListView:

 ScrollListener scrollListener = new ScrollListener(yourListView);
 scrollListener.enable();

MouseEvent.MOUSE_CLICKED расходуется при прокрутке списка

person jns    schedule 30.06.2016

Я нашел другое решение этой проблемы:

public class ScrollList {

protected double lastYposition = 0;
private double last_value = 0;
protected double firstYposition = 0;
protected ScrollBar scrollbar = null;
private int autoValue = 0;
private int maxScroll = 0;

public ScrollList(ListView list) {

list.setOnMousePressed(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {

        lastYposition = event.getSceneY();
        //Config.prints(lastYposition + "");

        firstYposition = lastYposition;

    }
});

Platform.runLater(new Runnable() {
    @Override
    public void run() {
        if (scrollbar == null) {
            for (Node node : list.lookupAll(".scroll-bar")) {
                if (node instanceof ScrollBar) {
                    ScrollBar bar = (ScrollBar) node;
                    if (bar.getOrientation().equals(Orientation.VERTICAL)) {
                        scrollbar = bar;
                    }
                }
            }

            scrollbar.setOnMouseReleased(new EventHandler<MouseEvent>() {
                @Override
                public void handle(MouseEvent event) {

                    lastYposition = event.getSceneY();

                    last_value = autoValue;

                }
            });

            /* //in case the list has already a scroll value, like when 
            //return to fontes list, it should has the last choosed fontes selected 
            IndexedCell first = flow.getFirstVisibleCellWithinViewPort();
            autoValue = first.getIndex();*/
            VirtualFlow flow = (VirtualFlow) list.lookup(".virtual-flow");
            last_value = autoValue;
            maxScroll = flow.getCellCount();

            flow.addEventFilter(Event.ANY, new EventHandler<Event>() {
                @Override
                public void handle(Event eventus) {
                    IndexedCell first = flow.getFirstVisibleCellWithinViewPort();
                    autoValue = first.getIndex();
                    Config.prints(last_value + "");
                }
            });
        }
    }
});

list.setOnMouseDragged(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {

        double newYposition = event.getSceneY();

        //Config.prints(newYposition + "");
        double diff = newYposition - lastYposition;
        lastYposition = newYposition;

        if ((firstYposition - lastYposition) > Config.TOUCH_SCREEN_NOISE && (firstYposition - lastYposition) > 0 || (firstYposition - lastYposition) < (Config.TOUCH_SCREEN_NOISE * -1) && (firstYposition - lastYposition) < 0) {
            list.getSelectionModel().clearSelection();
        }

        if (diff > 0 && diff > Config.MIN_SCROLL_VELOCITY) {
            diff = Config.MIN_SCROLL_VELOCITY;
        } else if (diff < 0 && diff < Config.MIN_SCROLL_VELOCITY * -1) {
            diff = Config.MIN_SCROLL_VELOCITY * -1;
        }

        if (diff > 0 && diff > Config.MAX_SCROLL_VELOCITY) {
            diff = Config.MAX_SCROLL_VELOCITY;
        } else if (diff < 0 && diff < Config.MAX_SCROLL_VELOCITY * -1) {
            diff = Config.MAX_SCROLL_VELOCITY * -1;
        }

        //prints("diff=>" + diff + " || last_value=>" + last_value);
        last_value -= diff;
        //Config.prints("last_value=>" + last_value);
        if (last_value < 0) {
            last_value = 0;
        }else if(last_value > maxScroll){
            last_value = maxScroll;
        } 
        list.scrollTo((int) last_value);
    }
});

}

//if you want the scroll to start from a specific position

    public void setAutoValue(int index) {


       this.autoValue = index;
    }
  }
}

public static double MAX_SCROLL_VELOCITY = 1;

public static double MIN_SCROLL_VELOCITY = 0.5;

Я поместил этот код в объект «ScrollList» и использовал его вот так.

protected ArrayList<ScrollList> scrolls = new ArrayList<>();     
protected void setScroll(ListView list, int... index) {

   ScrollList scrollList = new ScrollList(list);

   if (index.length > 0) {
       scrollList.setAutoValue(index[0]);
   }

   scrolls.add(scrollList);

}

ArrayList - это потому, что в моем случае у меня есть несколько представлений, у меня есть более одного представления списка.

GC

person Odracir    schedule 30.06.2016

Сделать это просто:

import com.sun.javafx.scene.control.skin.VirtualFlow;
import com.sun.javafx.scene.control.skin.VirtualScrollBar;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;

public class ListViewHelper
{
    public static VirtualFlow getVirtualFlow(ListView lv)
    {
        return (VirtualFlow) lv.lookup(".virtual-flow");
    }

    public static VirtualScrollBar getVbar(VirtualFlow vf)
    {
        try {
            final Method method = VirtualFlow.class.getDeclaredMethod("getVbar");
            method.setAccessible(true);
            return (VirtualScrollBar) method.invoke(vf);
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException |
                InvocationTargetException ex) {
            throw new RuntimeException(ex);
        }
    }

    public static VirtualScrollBar getHbar(VirtualFlow vf)
    {
        try {
            final Method method = VirtualFlow.class.getDeclaredMethod("getHbar");
            method.setAccessible(true);
            return (VirtualScrollBar) method.invoke(vf);
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException |
                InvocationTargetException ex) {
            throw new RuntimeException(ex);
        }
    }

}

Просто вызовите getVbar (getVirtualFlow (listViewInstance)), и в ваших руках будет полоса прокрутки. Затем вы можете добавить слушателя к значению свойства полосы прокрутки.

person Amin    schedule 03.09.2016