【问题标题】:Gluon Mobile ScrollPane OptimizationGluon Mobile ScrollPane 优化
【发布时间】:2018-01-28 18:04:46
【问题描述】:

我正在尝试使用嵌套的 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 进行测试。

这是有关该问题的简短视频。刷完之后就可以看到了。滚动停止一秒钟,然后继续:

【问题讨论】:

  • 我还没有测试过你的代码,但是为什么你说你不能在 Scene Builder 中使用 CharmListView 呢?到目前为止,ListView/CharmListView 控件精确地用于虚拟化和仅几个单元格的重用。而且,在移动端,CharmListView 实际上是一个优化的 ListView。
  • 我当然可以在SceneBuilder中添加CharmListView,但是如何在SceneBuilder中添加HBox呢?对于此应用程序中的其他情况,我实际上已采用硬编码扩展 CharmListView 的 HBox 列表视图。但是,此应用程序中的大多数视图都是使用带有 FXML 注入的 SceneBuilder 完成的,因此我想限制生成视图所需的硬编码量。这不是一个很长的清单。只是足够长,无法适应视图。上面的代码 sn-p 只是重新创建问题的一些示例代码。不是实际代码。
  • 这也不是一个真正的列表。它只是一个很长的表单,可能会根据查看它的设备的大小而溢出。
  • 与常规 ListView 一样,CharmListView 使用 ListCell 实现。它们都不支持 Scene Builder 中的单元格。所以那里无法实现。但如果它也不适合作为列表,那么您确实需要另一个解决方案。
  • 我猜你受到this bug的影响。可悲的是,似乎没有人对修复它太感兴趣。

标签: gluon gluon-mobile javafxports


【解决方案1】:

A.Sharma 提供的答案解决了一些问题,因为它完全删除了内置 ScrollPane 事件处理的底层实现。

当然,要明确地解决问题,JavaFXPorts 中需要适当的解决方案。

与此同时,另一种与现有实现更紧密合作的可能方法可以通过直接在ScrollPaneSkin 中解决问题来完成,而ScrollPaneSkin 又是负责控件事件处理的类。

课程可以在here找到。

好消息是这个类可以克隆到您的项目中(即MyScrollPaneSkin),修改并添加为现有ScrollPane控件的皮肤。

可能的修复

经过一些测试,问题的罪魁祸首可以定位在contentsToViewTimeline动画中。它会暂停 1.35 秒以:

挡住“余震”

然后在滚动事件处理程序中有这一行,它检查动画是否已经结束,然后只更新滚动条的位置:

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

虽然不应删除此动画,但即使动画处于空闲状态,也应更新滚动条。

我进行了以下更改,将第 517-527 行替换为:

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

在您的项目上,现在替换内置皮肤:

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

我在 Android 和 iOS 上测试过,两种情况下的动画都很流畅,没有任何问题的痕迹。

此外,这些属性可能会有所帮助(使用添加到 /src/main/resourcesjava.custom.properties):

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

如果可行,它可以提交给 JavaFXPorts 问题或作为 PR。

【讨论】:

  • José 用于自定义属性文件..是否只需将其输入到资源文件夹中?还是我也必须在 Java 代码中的任何地方引用它?
  • 或者我可以用 System.setProperties() 方法设置它吗?
  • 只需添加具有给定名称的属性文件。
【解决方案2】:

所以我通过使用本机滚动事件并对其进行自定义来解决此问题。 我基本上使用了一种试错法来确定我需要哪些硬(以下因素)值才能使其滚动正常。

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+ 上的应用程序中,这实际上运行良好。当我将浏览器注入带有 web 视图的应用程序时,它的效果相当好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多