Gluon Mobile ScrollPane Optimization

こ雲淡風輕ζ 提交于 2019-12-22 14:39:55

问题


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:


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/48489652/gluon-mobile-scrollpane-optimization

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