Gluon Mobile ScrollPane优化

时间:2018-01-28 18:04:46

标签: gluon gluon-mobile javafxports

我正在尝试使用嵌套的VBox实现JavaFX ScrollPane,在移动设备中滚动速度过快时,我遇到了一个奇怪的问题。问题是,如果我滚动较小但快速向上的手势,滚动窗格会先停止半秒钟,然后继续。随机滑动手势会变得迟钝。无论如何我可以优化这个吗?

这不会发生在桌面版本上,所以我猜这可能是我的手机和JavaFX本身的限制。 当我为滚动窗格视口设置背景图像时,此问题更加明显。以下是视图的示例代码:

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")));
    }

}

最终,我试图让它尽可能接近实际的原生移动环境。

我已经能够优化需要滚动的情况,创建一个以HBox为单元格的自定义CharmListView,但是我不能将SceneBuilder用于影响可维护性的视图。

注意:我正在使用LG G5进行测试。

以下是该问题的简短视频。滑动后即可看到。滚动停止一秒钟,然后继续:

Scroll lag display.

2 个答案:

答案 0 :(得分:2)

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.

答案 1 :(得分:1)

所以我通过使用本机滚动事件并自定义它来破解修复此问题。 我基本上使用了一种反复试验的方法来弄清楚我需要什么样的硬度(以下因素)才能让它滚动好。

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();
            });
        }

    });
}

您可以通过执行以下操作将此覆盖限制为仅在移动设备上发生:

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

如果有人可以对此进行优化或提出自己的解决方案,那就太棒了。同样,我的目标主要是防止在滚动时出现滞后停止。

在iPhone 7+的应用程序中,这实际上非常有效。当我使用网络视图将浏览器注入应用程序时,它的效果相当好。