如何在拖动和放大或缩小图像后重新居中

How to re-center an image once it has been dragged and zoomed in or out

我正在尝试创建一个 class 来处理缩放和平移组件。 class 包含对 Anchorpane 的引用,我正在使用它,因为它默认情况下不进行任何对齐,以及我希望能够拖动和缩放到的 ImageView。缩放和平移都可以单独工作,但是在放大或缩小图像后我无法重新居中。

我的想法是,当应用程序调整大小时,我希望能够使图像重新居中,以便拖动看起来总是相对于 Anchorpane 的中心。默认情况下,Anchorpane 中的任何位移都是相对于左上角的,但这对用户来说并不直观。内容看起来相对于中心移动会更合乎逻辑。为实现这一点,想法是每当 window 改变大小时将内容重新居中,然后应用与用户所做的拖动量相对应的翻译

如果您 运行 我发布的代码并放大,然后调整 window 的大小,您会注意到代表图像的红色矩形到处都是。如果缩小 window,那么正方形将不再像以前一样在同一个地方。这仅在已应用缩放并且似乎是重新居中方法的问题时才会发生。

如果居中方法工作正常,正方形应该 return 到扩展 window 之前的相同位置,这发生在正方形为 1 比 1 比例并且没有已发生缩放

这是我用来处理缩放和拖动的class

package com.example.test;

import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;

public class ZoomController{

    // the plane on which the node is being dragged
    private final AnchorPane PLANE;
    // the node I want to zoom and drag
    private final Node CONTENT;

    private Point2D lastContentPosition = new Point2D(0, 0);
    private Point2D lastMousePosition   = new Point2D(0, 0);
    // the total amount of dragging 
    // applied to the CONTENT node
    private Point2D dragOffset          = new Point2D(0, 0);

    // the total amount of scaling
    // applied to the CONTENT node
    private int scale = 1;

    public ZoomController(
            AnchorPane plane,
            Node content
    ) {
        this.PLANE = plane;
        this.CONTENT = content;

        addListeners();
        
        // artificially reproduces the problem
        applyZoom(2, new Point2D(0, 350));
        applyDrag(new Point2D(-100, 0));
    }

    private void addListeners() {
        // tries to center the CONTENT whenever the window is resized
        PLANE.heightProperty().addListener(centerContent());
        PLANE.widthProperty().addListener(centerContent());

        // saves the mouse's position whenever the mouse moves
        PLANE.setOnMousePressed(getMousePosition());
        PLANE.setOnMouseDragged(drag());

        PLANE.setOnScroll(handleScroll());
    }

    private ChangeListener<Number> centerContent() {
        return (observableValue, number, t1) -> {
            centerView();
        };
    }

    private EventHandler<MouseEvent> drag() {
        return mouseEvent -> {

            // calculates the path taken by the mouse...
            Point2D newMousePosition = new Point2D(mouseEvent.getX(), mouseEvent.getY());
            Point2D mouseTranslation = lastMousePosition.subtract(newMousePosition);
            // ...and saves its new position
            updateMousePosition(mouseEvent);

            // applies the drag
            applyDrag(mouseTranslation);

        };
    }

    private EventHandler<MouseEvent> getMousePosition() {
        return this::updateMousePosition;
    }

    private EventHandler<ScrollEvent> handleScroll() {
        return scrollEvent -> {

            // filters out the mouse stopping to scroll
            if (scrollEvent.getDeltaX() == 0 && scrollEvent.getDeltaY() == 0) return;

            // starts zooming
            if (scrollEvent.isControlDown()) {
                zoom(scrollEvent);
            }

        };
    }

    private void zoom(ScrollEvent scrollEvent) {

        // adds or subtracts to the image's scale based on
        // whether user is scrolling backwards or forwards
        final double dScale = scrollEvent.getDeltaY() > 0 ? 0.1 : -0.1;
        scale += dScale;

        // gets the coordinates IN THE IMAGE's FRAME OF REFERENCE
        // of the point at which to zoom the image so it is centered on the mouse
        Point2D target = CONTENT.parentToLocal(new Point2D(scrollEvent.getX(), scrollEvent.getY()));

        // applies the zoom to the image
        applyZoom(1 + dScale, target);

        // saves the image's position once it has been zoomed
        updateContentPosition();
    }

    private void applyZoom(final double zoomAmount, Point2D target) {
        // applies the necessary scaling to the image...
        Scale zoom = new Scale(zoomAmount, zoomAmount);
        // ...and centers the scaling to the point where the mouse is located at
        zoom.setPivotY(target.getY());
        zoom.setPivotX(target.getX());
        CONTENT.getTransforms().add(zoom);

        updateContentPosition();
    }

    private void applyDrag(Point2D dragAmount) {
        // drag amount always corresponds to the mouse's displacement
        // for the moment this is a 1 to 1 mapping
        // since I have not figured out how to take the scale into consideration

        // updates the total displacement caused by drag (used when we re-center the image)
        dragOffset = dragOffset.subtract(dragAmount);

        // applies the necessary translation to the image...
        Translate drag = new Translate();
        // ...based on the mouse's movement
        drag.setX(-dragAmount.getX());
        drag.setY(-dragAmount.getY());
        CONTENT.getTransforms().add(drag);

        // saves the image's position after it has been dragged
        updateContentPosition();
    }

    private void centerView() {
        // gets the coordinates we need to place the image at for it to be centered
        Point2D centerPosition = getCenterEdge();
        // calculates the path to take from the image's current position
        // to the position it has to be at to be centered
        // ie: the displacement vector
        Point2D translation    = centerPosition.subtract(lastContentPosition);

        // applies the necessary translation to the image...
        Translate translateToCenter = new Translate();
        // ...while account for drag so image is not fully re-centered
        translateToCenter.setX(translation.getX() + dragOffset.getX());
        translateToCenter.setY(translation.getY() + dragOffset.getY());
        CONTENT.getTransforms().add(translateToCenter);

        // saves the image's position after it has been centered
        updateContentPosition();
    }

    private void updateMousePosition(MouseEvent mouseEvent) {
        lastMousePosition = new Point2D(mouseEvent.getX(), mouseEvent.getY());
    }

    private void updateContentPosition() {
        // updates the image's position
        lastContentPosition = getContentPosition();
    }

    private Point2D getContentPosition() {
        // gets the minimal coordinates of the bounds around the image
        // ie: the image's coordinates
        Bounds contentBounds = CONTENT.getBoundsInParent();
        return new Point2D(contentBounds.getMinX(), contentBounds.getMinY());
    }

    private Point2D getCenterEdge() {
        // gets the size of the image and the anchor pane it is in...
        Point2D contentSize    = getContentSize();
        Point2D availableSpace = getAvailableSpace();
        // ...to determine the coordinates at which to place the image for it to be centerd
        return new Point2D(
                (availableSpace.getX() - contentSize.getX()) / 2,
                (availableSpace.getY() - contentSize.getY()) / 2
        );
    }

    private Point2D getContentSize() {
        // gets the bounds around the image
        Bounds contentBounds = CONTENT.getBoundsInParent();
        return new Point2D(contentBounds.getWidth(), contentBounds.getHeight());
    }

    private Point2D getAvailableSpace() {
        // gets the size of the Anchorpane the image is inn
        return new Point2D(PLANE.getWidth(), PLANE.getHeight());
    }


}

这是主要的class我用来测试缩放

package com.example.test;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class ZoomMain extends Application {

    @Override
    public void start(Stage stage) throws Exception {

        // the node we want to drag & zoom
        Rectangle rectangle = new Rectangle(200, 100);
        rectangle.setFill(Color.RED);

        // the plane on which the node is being dragged
        AnchorPane plane    = new AnchorPane();

        // adds the node
        Scene mainScene = new Scene(plane);
        plane.getChildren().add(rectangle);

        // handles the zoom
        ZoomController zoomController = new ZoomController(
            plane,
            rectangle
        );

        stage.setTitle("Zooming test");
        stage.setScene(mainScene);
        stage.setMinHeight(500);
        stage.setMinWidth(500);

        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}

我已经尝试将图像分组以拖动和放大到一个组中,这将很有用,因为它可以让我一次包含多个要拖动的节点,但是当我这样做时,组的内容仍然存在看不见。

不要在缩放后重新居中内容,而是使用内容的中心点作为缩放轴心。 换句话说,围绕内容的中心缩放。
介绍一个简单的计算中心的方法:

private Point2D getContentCenter() {
    Bounds contentBounds = CONTENT.getBoundsInLocal();
    return new Point2D(contentBounds.getCenterX(), contentBounds.getCenterY());
}

并围绕它缩放:

private void zoom(ScrollEvent scrollEvent) {
    // adds or subtracts to the image's scale based on
    // whether user is scrolling backwards or forwards
    final double dScale = scrollEvent.getDeltaY() > 0 ? 0.1 : -0.1;
    scale += dScale;
    Point2D target  = getContentCenter();
    // applies the zoom to the image
    applyZoom(1 + dScale, target);
}

编辑: 以下 ZoomController 在调整窗格大小时实现围绕嘴巴位置缩放和重新居中的内容:

class ZoomController{

    // the plane on which the node is being dragged
    private final AnchorPane PLANE;
    // the node I want to zoom and drag
    private final Node CONTENT;

    // the total amount of scaling  applied to the CONTENT node
    private int scale = 1;

    private Point2D lastMousePosition   = new Point2D(0, 0);

    public ZoomController( AnchorPane plane, Node content ) {
        PLANE = plane;
        CONTENT = content;

        PLANE.heightProperty().addListener((obs, number, t1) -> centerView());
        PLANE.widthProperty().addListener((obs, number, t1) -> centerView());

        // saves the mouse's position whenever the mouse moves
        PLANE.setOnMousePressed(event -> updateMousePosition(event));
        PLANE.setOnMouseDragged(drag());

        PLANE.setOnScroll(handleScroll());
    }

    private void updateMousePosition(MouseEvent mouseEvent) {
        lastMousePosition = new Point2D(mouseEvent.getX(), mouseEvent.getY());
    }

    private EventHandler<ScrollEvent> handleScroll() {
        return scrollEvent -> {
            // filters out the mouse stopping to scroll
            if (scrollEvent.getDeltaX() == 0 && scrollEvent.getDeltaY() == 0) return;
            // starts zooming
            if (scrollEvent.isControlDown()) {
                zoom(scrollEvent);
            }
        };
    }

    private void zoom(ScrollEvent scrollEvent) {

        // adds or subtracts to the image's scale based on
        // whether user is scrolling backwards or forwards
        final double dScale = scrollEvent.getDeltaY() > 0 ? 0.1 : -0.1;
        scale += dScale;

        // scale around mouse position
        Point2D pivot = CONTENT.parentToLocal(new Point2D(scrollEvent.getX(), scrollEvent.getY()));

        //applies the zoom to the image
        applyZoom(1 + dScale, pivot);
    }

    private void applyZoom(final double zoomAmount, Point2D target) {
        // applies the necessary scaling to the image...
        Scale zoom = new Scale(zoomAmount, zoomAmount);
        // ...and centers the scaling to the point where the mouse is located at
        zoom.setPivotY(target.getY());
        zoom.setPivotX(target.getX());
        CONTENT.getTransforms().add(zoom);
    }

    private EventHandler<MouseEvent> drag() {
        return mouseEvent -> {

            // calculates the path taken by the mouse...
            Point2D newMousePosition = new Point2D(mouseEvent.getX(), mouseEvent.getY());
            Point2D mouseTranslation = lastMousePosition.subtract(newMousePosition);
            // ...and saves its new position
            updateMousePosition(mouseEvent);

            // applies the drag
            applyDrag(mouseTranslation);
        };
    }

    private void applyDrag(Point2D dragAmount) {
        // applies the necessary translation to the image...
        Translate drag = new Translate();
        // ...based on the mouse's movement
        drag.setX(-dragAmount.getX());
        drag.setY(-dragAmount.getY());
        CONTENT.getTransforms().add(drag);
    }

    private void centerView() {

        // gets the coordinates we need to place the image at for it to be centered
        Point2D centerPosition = CONTENT.parentToLocal(getCenterEdge());
        Point2D position = getContentPosition();

        Point2D translation    = centerPosition.subtract(position);
        // applies the necessary translation to the image...
        Translate translateToCenter = new Translate();
        translateToCenter.setX(translation.getX());
        translateToCenter.setY(translation.getY());
        CONTENT.getTransforms().add(translateToCenter);
    }

    private Point2D getContentPosition() {
        // gets the minimal coordinates of the bounds around the image
        // ie: the image's coordinates
        Bounds contentBounds = CONTENT.getBoundsInLocal();
        return new Point2D(contentBounds.getMinX(), contentBounds.getMinY());
    }

    private Point2D getCenterEdge() {
        // gets the size of the image and the anchor pane it is in...
        Point2D contentSize  = getContentSize();
        Point2D parentSize = getParentSize();
        // ...to determine the coordinates at which to place the image for it to be centered
        return new Point2D(
                (parentSize.getX() - contentSize.getX()) / 2,
                (parentSize.getY() - contentSize.getY()) / 2
        );
    }

    private Point2D getContentSize() {
        //BoundsInParent: rectangular bounds of this Node which include its transforms
        Bounds contentBounds = CONTENT.getBoundsInParent();
        return new Point2D(contentBounds.getWidth(), contentBounds.getHeight());
    }

    private Point2D getParentSize() {//renamed from getAvailableSpace()
        // gets the size of the Anchorpane the image is inn
        return new Point2D(PLANE.getWidth(), PLANE.getHeight());
    }
}