JavaFx 8 - 相对于鼠标位置缩放/缩放 ScrollPane

JavaFx 8 - Scaling / zooming ScrollPane relative to mouse position

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

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

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

我的代码:

@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 的方法?

我认为这是 this question which involves the same concepts at work. If you don't really care if it zooms relative to your mouse and just prefer it zoom in the center look at 问题的重复。如果您需要更多帮助,请在下方评论。

假设您希望具有以下缩放行为: 按下鼠标滚轮时 forward/backward 光标下的对象将被缩放 up/down 并且光标下的区域现在在缩放区域内居中。 例如。在指向缩放区域中心左侧的位置的同时向前推动滚轮会导致“放大并向右移动”动作。

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

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

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

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

Defines the factor by which coordinates are scaled about the center of the object along the X axis of this Node.

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

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

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

这样做可以减少对额外 Delta-class 的需求,并且鼠标位置的计算始终与当前的缩放因子保持一致。

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

有点像这样:

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

这是一个可缩放、可平移 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()));
    }
}