Gluon Mobile ScrollPane Optimization

↘锁芯ラ 提交于 2019-12-06 05:22:09

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.

So I kind of hacked a fix for this by consuming the native scroll event and customizing it. I essentially used a trial-and-error method to figure out what hard (factors below) values I needed to make it scroll OK.

private SimpleDoubleProperty location = new SimpleDoubleProperty();
private SimpleDoubleProperty vValue = new SimpleDoubleProperty(0);
private SimpleLongProperty startTime = new SimpleLongProperty();
private SimpleLongProperty timer = new SimpleLongProperty();
private double height = MobileApplication.getInstance().getGlassPane().getHeight();
private Animation animation;
private Animation callbackAnimation;

private void scrollOverride() {

    sp.addEventFilter(ScrollEvent.SCROLL, event -> {
        event.consume();
    });

    sp.setOnTouchPressed(e -> {

        if (callbackAnimation != null){
            callbackAnimation.stop();
        }

        if (animation != null) {
            animation.stop();
        }

        vValue.set(sp.vvalueProperty().get());
        location.set(e.getTouchPoint().getY());
        startTime.set((new Date()).getTime());

    });

    sp.setOnTouchMoved(e -> {
        timer.set((new Date()).getTime());
    });

    sp.setOnTouchReleased(e -> {

        Boolean dontOverride = false;

        double deltaTime = (new Date()).getTime() - startTime.get();
        double deltaY = sp.vvalueProperty().get() - vValue.get();

        if(Math.abs(deltaY) < 0.05){
            dontOverride = true;
        }

        double translation = deltaY / (deltaTime/300);

        double value = 0d;

        if (Math.abs(timer.get() - startTime.get()) < 500) {
            value = sp.vvalueProperty().get() + translation;
        } else {
            value = sp.vvalueProperty().get();
            dontOverride = true;
        }

        animation = new Timeline(
                new KeyFrame(Duration.millis(deltaTime + 100),
                        new KeyValue(sp.vvalueProperty(), value)));

        animation.play();

        if (!dontOverride) {
            animation.setOnFinished(evt -> {
                boolean innerUp = (sp.vvalueProperty().get() - vValue.get()) < 0;

                int innerSign = 1;

                if (innerUp) {
                    innerSign = -1;
                }

                callbackAnimation = new Timeline(
                        new KeyFrame(Duration.millis(deltaTime + 150),
                                new KeyValue(sp.vvalueProperty(), sp.vvalueProperty().get() + (0.1 * innerSign), Interpolator.EASE_OUT)));
                callbackAnimation.play();
            });
        }

    });
}

You can limit this override to only happen on mobile devices by doing the following:

if(!com.gluonhq.charm.down.Platform.isDesktop()){
   scrollOverride();
}      

If anybody can optimize this or come up with their own solution, that would be great. Again, my goal was primarily to prevent that laggy stop from occuring when scrolling.

Within the application on my iPhone 7+, this is actually working very well. It works comparably well as when I inject a browser into the app with a web view.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!