【问题标题】:JavaFx 8 - Scaling / zooming ScrollPane relative to mouse positionJavaFx 8 - 相对于鼠标位置缩放/缩放 ScrollPane
【发布时间】:2017-02-11 04:19:14
【问题描述】:

我需要相对于鼠标位置放大/缩小滚动窗格。

我目前通过将我的内容包装在一个组中并缩放组本身来实现缩放功能。我使用自定义枢轴创建了一个新的 Scale 对象。 (Pivot设置为鼠标位置)

这非常适用于 Group 的初始比例为 1.0,但之后的缩放不会在正确的方向上缩放 - 我相信这是因为当 Group 被缩放时相对鼠标位置发生了变化。

我的代码:

@Override
public void initialize(URL location, ResourceBundle resources) {

    Delta initial_mouse_pos = new Delta();

    anchorpane.setOnScrollStarted(event -> {
        initial_mouse_pos.x = event.getX();
        initial_mouse_pos.y = event.getY();
    });

    anchorpane.setOnScroll(event -> {
        double zoom_fac = 1.05;
        double delta_y = event.getDeltaY();

        if(delta_y < 0) {
            zoom_fac = 2.0 - zoom_fac;
        }

        Scale newScale = new Scale();
        newScale.setPivotX(initial_mouse_pos.x);
        newScale.setPivotY(initial_mouse_pos.y);
        newScale.setX( content_group.getScaleX() * zoom_fac );
        newScale.setY( content_group.getScaleY() * zoom_fac );

        content_group.getTransforms().add(newScale);

        event.consume();
    });
}

private class Delta { double x, y; }

如何在不同的缩放级别获得正确的鼠标位置?是否有一种完全不同的方式可以更轻松地缩放 ScrollPane?

【问题讨论】:

标签: java javafx javafx-8 zooming scrollpane


【解决方案1】:

我相信这是 this 问题的重复,其中涉及相同的概念。如果您真的不在乎它是否相对于您的鼠标缩放,而只是更喜欢它在中心放大,请查看this 问题。如果您需要更多帮助,请在下方评论。

【讨论】:

    【解决方案2】:

    假设您希望具有以下缩放行为: 当鼠标滚轮向前/向后推动时,光标下的对象将被放大/缩小,光标下的区域现在位于缩放区域的中心。 例如。在指向缩放区域中心左侧的位置时向前推动滚轮会导致“向上缩放并向右移动”动作。

    缩放的事情就像你到目前为止所做的一样简单。 棘手的部分是移动动作。您必须在计算中考虑一些问题:

    1. 您必须计算与中心和 光标的位置。您可以通过减去来计算此值 距离鼠标位置 (mp) 的中心点 (cp)。

    2. 如果您的缩放级别为 1,并且您从中心向左指向 50 像素,则您希望将对象向右移动 50 像素,因为屏幕的 1 像素对应于对象(图片)的 1 像素。但是,如果您将对象的大小加倍,则 2 个屏幕像素等于对象像素。移动对象时必须考虑这一点,因为平移部分总是在缩放部分之前完成。换句话说,您正在以原始大小移动对象,而缩放只是第二个独立步骤。

    3. 缩放是如何完成的?在 JavaFX 中,您只需设置一些比例属性,JavaFX 会为您完成其余的工作。但它究竟是什么?重要的是要知道,在缩放对象时固定点在哪里。如果你缩放一个对象,将会有一个点保持固定在其位置,而所有其他点都向该点移动或从它移动。 正如 JavaFX 的文档所说,缩放对象的中心将是固定点。

    定义坐标绕中心缩放的因子 沿此节点 X 轴的对象。

    这意味着您必须确保您的视觉中心点等于 JavaFX 在缩放对象时使用的中心点。如果将对象包装在容器中,则可以实现此目的。现在缩放容器而不是对象,并将对象放置在容器内以满足您的需要。

    我希望这会有所帮助。如果您需要更多帮助,请提供一个简短的工作示例项目。

    【讨论】:

      【解决方案3】:

      您是否尝试删除 setOnScrollStarted 事件并将其内容移动到 setOnScroll 事件?

      这样做可以减少对额外 Delta 类的需求,并且鼠标位置的计算始终与当前缩放系数相当。

      我实现了同样的东西,它按照你描述的方式工作。

      有点像这样:

      @Override
      public void initialize(URL location, ResourceBundle resources) {
      
          anchorpane.setOnScroll(event -> {
              double zoom_fac = 1.05;
      
              if(delta_y < 0) {
                  zoom_fac = 2.0 - zoom_fac;
              }
      
              Scale newScale = new Scale();
              newScale.setPivotX(event.getX);
              newScale.setPivotY(event.getY);
              newScale.setX( content_group.getScaleX() * zoom_fac );
              newScale.setY( content_group.getScaleY() * zoom_fac );
      
              content_group.getTransforms().add(newScale);
      
              event.consume();
          });
      }
      

      【讨论】:

        【解决方案4】:

        这是一个可扩展、可平移 JavaFX ScrollPane:

        import javafx.geometry.Bounds;
        import javafx.geometry.Point2D;
        import javafx.geometry.Pos;
        import javafx.scene.Group;
        import javafx.scene.Node;
        import javafx.scene.control.ScrollPane;
        import javafx.scene.layout.VBox;
        
        public class ZoomableScrollPane extends ScrollPane {
            private double scaleValue = 0.7;
            private double zoomIntensity = 0.02;
            private Node target;
            private Node zoomNode;
        
            public ZoomableScrollPane(Node target) {
                super();
                this.target = target;
                this.zoomNode = new Group(target);
                setContent(outerNode(zoomNode));
        
                setPannable(true);
                setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
                setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
                setFitToHeight(true); //center
                setFitToWidth(true); //center
        
                updateScale();
            }
        
            private Node outerNode(Node node) {
                Node outerNode = centeredNode(node);
                outerNode.setOnScroll(e -> {
                    e.consume();
                    onScroll(e.getTextDeltaY(), new Point2D(e.getX(), e.getY()));
                });
                return outerNode;
            }
        
            private Node centeredNode(Node node) {
                VBox vBox = new VBox(node);
                vBox.setAlignment(Pos.CENTER);
                return vBox;
            }
        
            private void updateScale() {
                target.setScaleX(scaleValue);
                target.setScaleY(scaleValue);
            }
        
            private void onScroll(double wheelDelta, Point2D mousePoint) {
                double zoomFactor = Math.exp(wheelDelta * zoomIntensity);
        
                Bounds innerBounds = zoomNode.getLayoutBounds();
                Bounds viewportBounds = getViewportBounds();
        
                // calculate pixel offsets from [0, 1] range
                double valX = this.getHvalue() * (innerBounds.getWidth() - viewportBounds.getWidth());
                double valY = this.getVvalue() * (innerBounds.getHeight() - viewportBounds.getHeight());
        
                scaleValue = scaleValue * zoomFactor;
                updateScale();
                this.layout(); // refresh ScrollPane scroll positions & target bounds
        
                // convert target coordinates to zoomTarget coordinates
                Point2D posInZoomTarget = target.parentToLocal(zoomNode.parentToLocal(mousePoint));
        
                // calculate adjustment of scroll position (pixels)
                Point2D adjustment = target.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));
        
                // convert back to [0, 1] range
                // (too large/small values are automatically corrected by ScrollPane)
                Bounds updatedInnerBounds = zoomNode.getBoundsInLocal();
                this.setHvalue((valX + adjustment.getX()) / (updatedInnerBounds.getWidth() - viewportBounds.getWidth()));
                this.setVvalue((valY + adjustment.getY()) / (updatedInnerBounds.getHeight() - viewportBounds.getHeight()));
            }
        }
        

        【讨论】:

        • 太好了,为我工作。另外:我建议将outerNode.setOnScroll(e -&gt; {...} 的内容包装在if (e.isControlDown()) {...} 中,这样可以保留上下滚动的能力。
        • @DanielHári 非常感谢兄弟。完美运行
        • 在我的例子中,事件监听器没有被调用。我必须直接在ZoomableScrollPane 上使用addEventFilter() 设置侦听器,如本答案stackoverflow.com/a/13342885/1502079 中所述。这也解决了当内容大小超过 ScrollPane 视口时不消耗 ScrollEvent 的问题。除此之外,它的工作原理,谢谢。
        猜你喜欢
        • 2014-04-22
        • 2015-06-12
        • 2012-10-20
        • 2020-05-28
        • 1970-01-01
        • 1970-01-01
        • 2016-04-14
        • 2022-01-06
        • 2021-10-15
        相关资源
        最近更新 更多