Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX Smooth Scrolling for ListView

I wonder if there is a way to tweak the scrolling of JavaFX's ListView.

You might say It's laggy because my collection is too large and/or my CellFactory is too heavy... it's just that the frame rate seems too low even with a small collection of Strings...

So I was wondering if there is a setting somewhere or guideline to follow to implement a smooth scrolling.

Thank you in advance

like image 844
Mackovich Avatar asked Oct 18 '22 17:10

Mackovich


1 Answers

This is JFoenix's smooth scrolling created for ScrollPanes, but slightly modified for ListViews instead (also works with JFoenix's JFXListView)

public class JFXSmoothScroll  {



private static ScrollBar getScrollbarComponent(ListView<?> control, Orientation orientation) {
    Node n = control.lookup(".scroll-bar");
    if (n instanceof ScrollBar) {
        final ScrollBar bar = (ScrollBar) n;
        if (bar.getOrientation().equals(orientation)) {
            return bar;
        }
    }

    return null;
}   

public static void smoothScrollingListView(ListView<?> listView, double speed) {
    smoothScrollingListView(listView, speed, Orientation.VERTICAL, bounds -> bounds.getHeight());
}

public static void smoothHScrollingListView(ListView<?> listView, double speed) {
    smoothScrollingListView(listView, speed, Orientation.HORIZONTAL, bounds -> bounds.getHeight());
}

private  static void smoothScrollingListView(ListView<?> listView, double speed, Orientation orientation, Function<Bounds, Double> sizeFunc) {
    ScrollBar scrollBar = getScrollbarComponent(listView, orientation);
    if (scrollBar == null) {
        return;
    }
    scrollBar.setUnitIncrement(5);
    final double[] frictions = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
    final double[] pushes = {speed};
    final double[] derivatives = new double[frictions.length];
    final double[] lastVPos = {0};
    Timeline timeline = new Timeline();
    final EventHandler<MouseEvent> dragHandler = event -> timeline.stop();
    final EventHandler<ScrollEvent> scrollHandler = event -> {
        scrollBar.valueProperty().set(lastVPos[0]);
        if (event.getEventType() == ScrollEvent.SCROLL) {
            double direction = event.getDeltaY() > 0 ? -1 : 1;
            for (int i = 0; i < pushes.length; i++) {
                derivatives[i] += direction * pushes[i];
            }
            if (timeline.getStatus() == Animation.Status.STOPPED) {
                timeline.play();
            }

        }
        event.consume();
    };
    if (scrollBar.getParent() != null) {
        scrollBar.getParent().addEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
        scrollBar.getParent().addEventHandler(ScrollEvent.ANY, scrollHandler);
    }
    scrollBar.parentProperty().addListener((o,oldVal, newVal)->{
        if (oldVal != null) {
            oldVal.removeEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
            oldVal.removeEventHandler(ScrollEvent.ANY, scrollHandler);
        }
        if (newVal != null) {
            newVal.addEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
            newVal.addEventHandler(ScrollEvent.ANY, scrollHandler);
        }
    });

    timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), (event) -> {
        for (int i = 0; i < derivatives.length; i++) {
            derivatives[i] *= frictions[i];
        }
        for (int i = 1; i < derivatives.length; i++) {
            derivatives[i] += derivatives[i - 1];
        }
        double dy = derivatives[derivatives.length - 1];
        double size = sizeFunc.apply(scrollBar.getLayoutBounds());
        scrollBar.valueProperty().set(Math.min(Math.max(scrollBar.getValue() + dy / size, 0), 1));
        lastVPos[0] = scrollBar.getValue();
        if (Math.abs(dy) < 1) {
            if (Math.abs(dy) < 0.001) {
                timeline.stop();
            } 
        } 
    }));
    timeline.setCycleCount(Animation.INDEFINITE);       
}

}

This is kinda hacky

final double[] lastVPos = {0};

because for some reason an event I cannot find triggers before firing the ScrollEvent and sets the initial scroll value before this. Method needs to be called after initializing the list the first time, otherwise it won't find the scrollbar

like image 74
mej71 Avatar answered Oct 21 '22 05:10

mej71