Search code examples
gluongluon-mobilejavafxports

Gluon Mobile ScrollPane Optimization


I'm trying to implement a JavaFX ScrollPane with a nested VBox, and I'm experience a strange issue when scrolling too fast in mobile. The issue is that if I scroll in smaller yet quick upward gestures, the scrollpane stalls for a half a second first, and then continues. It gets laggy in random swipe gestures. Is there anyway I can optimize this?

This doesn't happen on the desktop version, so I'm guessing it may be a limitation of my phone and JavaFX itself. This issue is more evident when I set a background image for the scroll pane viewport. Here is some sample code for a View:

public class StackUserView extends View {

    private Label label;

    public StackUserView(String name) {
        super(name);
        initDisplay();
    }

    private void initDisplay() {
        this.label = new Label("10");
        label.setFont(Font.font(40d));
        VBox box = new VBox();
        box.setSpacing(10);
        box.setAlignment(Pos.CENTER_RIGHT);

        for(int i = 0; i < 100; i++){
            Label label = new Label("HELLO");
            label.setCache(true);
            label.setCacheHint(CacheHint.SPEED);
            label.setCacheShape(true);
            box.getChildren().add(label);
        }

        ScrollPane pane = new ScrollPane(box);
        pane.setCacheHint(CacheHint.QUALITY);
        pane.setFitToHeight(true);
        pane.setFitToHeight(true);

        this.setCenter(pane);
    }

    @Override
    protected void updateAppBar(AppBar appBar) {
        appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> MobileApplication.getInstance().showLayer(SpeedSelect.MENU_LAYER)));
        appBar.setTitleText(this.getName());
        appBar.getActionItems().add(MaterialDesignIcon.SEARCH.button(e -> System.out.println("Search")));
    }

}

Ultimately, I am trying to get it to scroll as close as possible to the actual native mobile environments.

I have been able to optimize situations where scrolling is desired creating a custom CharmListView with HBox's as cells, but then I can't use SceneBuilder for my views which affects maintainability.

Note: I am using an LG G5 for testing.

Here is a short video of the issue. You can see after the swipe. The scroll stops for a second and then continues:

Scroll lag display.


Solution

  • The answer provided by A.Sharma solves some issues as it removes completely the underlying implementation of the built-in ScrollPane event handling.

    Of course, to solve the issue definitely, a proper solution is required in JavaFXPorts.

    In the meantime, another possible approach, that works more closely with the exiting implementation can be done by tackling the problem directly in ScrollPaneSkin, which in turn is the class responsible for the event handling for the control.

    The class can be found here.

    The good news is that this class can be cloned to your project (i.e MyScrollPaneSkin), modified and added as new skin for the exiting ScrollPane control.

    Possible fix

    After some tests, the culprit of the issue can be located in the contentsToViewTimeline animation. It performs a 1.35 second pause to:

    block out 'aftershocks'

    Then there is this line inside the scroll event handler, that checks if the animation has ended and only updates the scrollbar position after that:

    if (!(((ScrollEvent)event).isInertia()) || (((ScrollEvent)event).isInertia()) && 
        (contentsToViewTimeline == null || 
         contentsToViewTimeline.getStatus() == Status.STOPPED)) {
        vsb.setValue(newValue);
        ...
    }   
    

    While this animation shouldn't be removed, the scrollbar should be updated even when the animation is in idle.

    I've made the following change, replacing lines 517-527 with:

        /*
        ** if there is a repositioning in progress then we only
        ** set the value for 'real' events
        */
        if ((newValue <= vsb.getMax() && newValue >= vsb.getMin())) {
            vsb.setValue(newValue);
        }
        boolean stop = ! ((ScrollEvent) event).isInertia() || ((ScrollEvent) event).isInertia() && (contentsToViewTimeline == null || contentsToViewTimeline.getStatus() == Status.STOPPED);
        if ((newValue > vsb.getMax() || newValue < vsb.getMin()) && (!mouseDown && !touchDetected) && stop) {
            startContentsToViewport();
        }
        if (stop) {
            event.consume();
        }
    

    On your project, now replace the built-in skin:

     ScrollPane pane = new ScrollPane(box);
     pane.setSkin(new MyScrollPaneSkin(pane));
    

    I've tested it on Android and iOS and in both cases the animation is smooth and there is not trace of the issue at hand.

    Also, these properties might help (use a java.custom.properties added to /src/main/resources):

    gluon.experimental.performance=true
    com.sun.javafx.gestures.scroll.inertia.velocity=2000
    

    It this works, it could be submitted to the JavaFXPorts issue or as a PR.