首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >JavaFX ScrollPane更新viewportBoundsProperty

JavaFX ScrollPane更新viewportBoundsProperty
EN

Stack Overflow用户
提问于 2021-12-18 22:23:13
回答 2查看 158关注 0票数 0

我正在构建一个tilemap,就像openstreetmap或谷歌地图一样,使用JavaFX和一个ScrollPane和一个TilePane。

在tilemap中,我们只需要加载屏幕上当前可见的部分,而不是所有的块。

JavaFX ScrollPane似乎更新了它的viewportBounds MinXMaxXMinYMaxY (让我们将其称为offset ):

使用鼠标交互(单击并拖动)进行

  • ,但不是当使用垂直和水平滚动条时,也不是在使用

这是一个问题,因为这意味着,我可以计算当前可见的部分,我的内容,而不是当使用滚动条或移动使用触摸屏。

我需要不断地获得这个偏移量,因为当我的ScrollPane当前在ScrollPane“位置”可见时,它的内容是加载的:

下面是一个说明这种情况的最小可复制示例:

代码语言:javascript
复制
public class DemoScrollPane extends javafx.application.Application {
    private Logger logger = LoggerFactory.getLogger(DemoScrollPane.class);


    public static void run(String[] args) {
        javafx.application.Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        // create content
        Pane pane = new Pane();
        Rectangle rectangle = new Rectangle(1000,1000, Color.AQUA);
        Rectangle rectangleInitialVP = new Rectangle(640,480, Color.GRAY);
        pane.getChildren().add(rectangle);
        pane.getChildren().add(rectangleInitialVP);

        // create scrollPane
        ScrollPane scrollPane = new ScrollPane();
        scrollPane.setContent(pane);
        // add click and drag interaction
        scrollPane.setPannable(true);

        // register a watcher on scrollPane viewportBoundsProperty
        InvalidationListener listener =
                    o -> {
                        logger.debug("viewport change {}",scrollPane.getViewportBounds());
                    };
        // This is triggered when moving using click and drag (pan)
        // and does correctly update the scrollPane viewportbounds.        
        // BUT this is not trigered when moving by clocking on hbar or vbar or by using a touchpad (on mac) without clicking.
        scrollPane.viewportBoundsProperty().addListener(listener);

        // display the app
        Scene scene = new Scene(scrollPane, 640, 480);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    @Override
    public void stop() throws Exception {
        logger.debug("stop");
    }
}

有人能为我提供关于如何在任何滚动动作上动态计算(或获取)这个偏移量的指针(使用触摸屏或水平或垂直条单击并拖动摇摄或摇摄)吗?

提前谢谢。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-12-19 22:24:32

关于差异的问题的简单答案是::,这是如何实现的!!

首先,每次viewPort尝试布局其子节点时都会更新scrollPane边界。layoutChildren方法在ScrollPaneSkin类中的潜在峰值如下所示:

代码语言:javascript
复制
protected void layoutChildren(double x, double y, double w, double h) {
  ScrollPane control = (ScrollPane)this.getSkinnable();
  ... // All the stuff for layouting
  ...
  control.setViewportBounds(new BoundingBox(this.snapPosition(this.viewContent.getLayoutX()), this.snapPosition(this.viewContent.getLayoutY()), this.snapSize(this.contentWidth), this.snapSize(this.contentHeight)));
} 

现在,如果我们检查ScrollPane的"pannable“属性的相关实现,代码如下(在ScrollPaneSkin类中):

代码语言:javascript
复制
this.viewRect.setOnDragDetected((e) -> {
    if (IS_TOUCH_SUPPORTED) {
        this.startSBReleasedAnimation();
    }

    if (((ScrollPane)this.getSkinnable()).isPannable()) {
        this.dragDetected = true;
        if (this.saveCursor == null) {
            this.saveCursor = ((ScrollPane)this.getSkinnable()).getCursor();
            if (this.saveCursor == null) {
                this.saveCursor = Cursor.DEFAULT;
            }

            ((ScrollPane)this.getSkinnable()).setCursor(Cursor.MOVE);
            ((ScrollPane)this.getSkinnable()).requestLayout(); // !! THIS LINE !!
        }
    }

});
this.viewRect.addEventFilter(MouseEvent.MOUSE_RELEASED, (e) -> {
    this.mouseDown = false;
    if (this.dragDetected) {
        if (this.saveCursor != null) {
            ((ScrollPane)this.getSkinnable()).setCursor(this.saveCursor);
            this.saveCursor = null;
            ((ScrollPane)this.getSkinnable()).requestLayout();// !! THIS LINE !!
        }

        this.dragDetected = false;
    }

    if ((this.posY > ((ScrollPane)this.getSkinnable()).getVmax() || this.posY < ((ScrollPane)this.getSkinnable()).getVmin() || this.posX > ((ScrollPane)this.getSkinnable()).getHmax() || this.posX < ((ScrollPane)this.getSkinnable()).getHmin()) && !this.touchDetected) {
        this.startContentsToViewport();
    }

});

从上面的代码中,您可以注意到,当"pannable“属性为true时,如果检测到拖动,它将请求布局。在下一个场景脉冲中,它将调用layoutChildren并更新viewportBounds。当您释放鼠标时(如果检测到拖动),也会发生同样的情况。这就是为什么只能在平移开始和结束时才能看到日志,而不是在拖动时。

现在转到hbar/vbar拖动的代码,更新scrollBar值时的代码如下(在ScrollPaneSkin类中)

代码语言:javascript
复制
InvalidationListener vsbListener = (valueModel) -> {
    if (!IS_TOUCH_SUPPORTED) {
        this.posY = Utils.clamp(((ScrollPane)this.getSkinnable()).getVmin(), this.vsb.getValue(), ((ScrollPane)this.getSkinnable()).getVmax());
    } else {
        this.posY = this.vsb.getValue();
    }

    this.updatePosY();
};
this.vsb.valueProperty().addListener(vsbListener);
InvalidationListener hsbListener = (valueModel) -> {
    if (!IS_TOUCH_SUPPORTED) {
        this.posX = Utils.clamp(((ScrollPane)this.getSkinnable()).getHmin(), this.hsb.getValue(), ((ScrollPane)this.getSkinnable()).getHmax());
    } else {
        this.posX = this.hsb.getValue();
    }

    this.updatePosX();
};
this.hsb.valueProperty().addListener(hsbListener);  

值侦听器只是更新viewContent的layoutX/Y属性,而不是布局请求。updatePosX方法如下所示:

代码语言:javascript
复制
private double updatePosX() {
    ScrollPane sp = (ScrollPane)this.getSkinnable();
    double x = this.isReverseNodeOrientation() ? this.hsb.getMax() - (this.posX - this.hsb.getMin()) : this.posX;
    double minX = Math.min(-x / (this.hsb.getMax() - this.hsb.getMin()) * (this.nodeWidth - this.contentWidth), 0.0D);
    this.viewContent.setLayoutX(this.snapPosition(minX));
    if (!sp.hvalueProperty().isBound()) {
        sp.setHvalue(Utils.clamp(sp.getHmin(), this.posX, sp.getHmax()));
    }

    return this.posX;
}

另一个解决方案:

+1表示@James_D答案。如果您想保持这个更动态,或者如果您不知道您的窗格的最大界限,您可以尝试以下方法。

从上面的代码中,我们可以注意到,无论是平移还是拖动滚动条,viewContent(滚动窗格的内容父节点)的layoutX/Y属性都会被更新。因此,您可以将侦听器添加到其layoutX/Y属性中,这将为您提供准确的位置,而无需将min/max值设置为滚动条。

代码语言:javascript
复制
ScrollPane scrollPane = new ScrollPane();
scrollPane.setContent(pane);
scrollPane.getContent().parentProperty().addListener((obs, old, viewContent) -> {
    if (viewContent != null) {
        InvalidationListener posListener = e -> {
            double x = viewContent.getLayoutX() * -1;
            double y = viewContent.getLayoutY() * -1;
            double width = scrollPane.getViewportBounds().getWidth();
            double height = scrollPane.getViewportBounds().getHeight();
            Bounds movingViewPort = new BoundingBox(x, y, width, height);
            System.out.println("viewport: " + movingViewPort);
        };
        viewContent.layoutXProperty().addListener(posListener);
        viewContent.layoutYProperty().addListener(posListener);
    }
});
scrollPane.setPannable(true);
票数 1
EN

Stack Overflow用户

发布于 2021-12-19 18:43:55

感谢@James_D的评论,这里有一个解决方案来解决这个问题(见下文)。

如果有人对JavaFX的ScrollPane有更好的理解,请毫不犹豫地解释

使用不更新ScrollPane

  • vs的ScrollPane.

的viewPort的H和V条更新viewPort的单击和滚动

以下是从James_D的评论中获得的解决方案:

代码语言:javascript
复制
public class DemoScrollPane extends javafx.application.Application {
    private Logger logger = LoggerFactory.getLogger(DemoScrollPane.class);


    public static void run(String[] args) {
        javafx.application.Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        // create content
        Pane pane = new Pane();
        pane.setMaxSize(1000,1000);
        Rectangle rectangle = new Rectangle(1000,1000, Color.AQUA);
        Rectangle rectangleInitialVP = new Rectangle(640,480, Color.GRAY);
        pane.getChildren().add(rectangle);
        pane.getChildren().add(rectangleInitialVP);


        // create scrollPane
        ScrollPane scrollPane = new ScrollPane();
        scrollPane.setContent(pane);
        scrollPane.setVmin(0);
        scrollPane.setVmax(pane.getMaxHeight());
        scrollPane.setHmin(0);
        scrollPane.setHmax(pane.getMaxWidth());
        
        // add click and drag interaction
        scrollPane.setPannable(true);

        // register a watcher on scrollPane viewportBoundsProperty
        InvalidationListener listener =
                    o -> {
            Bounds movingViewPort = new BoundingBox(scrollPane.getHvalue(),
                    scrollPane.getVvalue(),
                    0.,
                    scrollPane.getViewportBounds().getWidth(),
                    scrollPane.getViewportBounds().getHeight(),
                    0.
                    );
                        logger.debug("viewport {}",movingViewPort);
                    };
        // This is triggered when moving using click and drag (pan)
        // and does correctly update the scrollPane viewportbounds.

        // BUT this is not trigered when moving by clocking on hbar or vbar or by using a touchpad (on mac)
        // without clicking.
        // (even tough the viewport obviously change)
        scrollPane.vvalueProperty().addListener(listener);
        scrollPane.hvalueProperty().addListener(listener);

        // display the app
        Scene scene = new Scene(scrollPane, 640, 480);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    @Override
    public void stop() throws Exception {
        logger.debug("stop");
    }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70407615

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档