在已缩放节点中的枢轴点处缩放

Posted

技术标签:

【中文标题】在已缩放节点中的枢轴点处缩放【英文标题】:Scale at pivot point in an already scaled node 【发布时间】:2015-02-06 00:35:51 【问题描述】:

我正在尝试使用可缩放/可平移的画布创建应用程序。

特点

在枢轴点使用鼠标滚轮放大/缩小 用鼠标左键在画布上拖动节点 用鼠标右键拖动整个画布

只要您以比例 1 开始缩放,轴点处的缩放就会起作用。将鼠标放在网格点上并滚动鼠标滚轮。枢轴点将保留在您开始缩放的位置。

问题

当您放大,然后将鼠标移动到另一个点并再次缩放时,轴心点会移动,并且在初始鼠标位置不再发生缩放。

示例

代码如下:

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Scale;
import javafx.stage.Stage;

/**
 * The canvas which holds all of the nodes of the application.
 */
class PannableCanvas extends Pane 

    Scale scaleTransform;

    public PannableCanvas() 

        setPrefSize(600, 600);
        setStyle("-fx-background-color: lightgrey; -fx-border-color: blue;");

        // add scale transform
        scaleTransform = new Scale( 1.0, 1.0);
        getTransforms().add( scaleTransform);

        // logging
        addEventFilter(MouseEvent.MOUSE_PRESSED, event ->  
            System.out.println( 
                    "canvas event: " + ( ((event.getSceneX() - getBoundsInParent().getMinX()) / getScale()) + ", scale: " + getScale())
                    );
            System.out.println( "canvas bounds: " + getBoundsInParent());   
                );

    

    /**
     * Add a grid to the canvas, send it to back
     */
    public void addGrid() 

        double w = getBoundsInLocal().getWidth();
        double h = getBoundsInLocal().getHeight();

        // add grid
        Canvas grid = new Canvas(w, h);

        // don't catch mouse events
        grid.setMouseTransparent(true);

        GraphicsContext gc = grid.getGraphicsContext2D();

        gc.setStroke(Color.GRAY);
        gc.setLineWidth(1);

        // draw grid lines
        double offset = 50;
        for( double i=offset; i < w; i+=offset) 
            // vertical
            gc.strokeLine( i, 0, i, h);
            // horizontal
            gc.strokeLine( 0, i, w, i);
        

        getChildren().add( grid);

        grid.toBack();
    

    public Scale getScaleTransform() 
        return scaleTransform;
    

    public double getScale() 
        return scaleTransform.getY();
    

    /**
     * Set x/y scale
     * @param scale
     */
    public void setScale( double scale) 
        scaleTransform.setX(scale);
        scaleTransform.setY(scale);
    

    /**
     * Set x/y pivot points
     * @param x
     * @param y
     */
    public void setPivot( double x, double y) 
        scaleTransform.setPivotX(x);
        scaleTransform.setPivotY(y);
    



/**
 * Mouse drag context used for scene and nodes.
 */
class DragContext 

    double mouseAnchorX;
    double mouseAnchorY;

    double translateAnchorX;
    double translateAnchorY;



/**
 * Listeners for making the nodes draggable via left mouse button. Considers if parent is zoomed.
 */
class NodeGestures 

    private DragContext nodeDragContext = new DragContext();

    PannableCanvas canvas;

    public NodeGestures( PannableCanvas canvas) 
        this.canvas = canvas;

    

    public EventHandler<MouseEvent> getOnMousePressedEventHandler() 
        return onMousePressedEventHandler;
    

    public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() 
        return onMouseDraggedEventHandler;
    

    private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() 

        public void handle(MouseEvent event) 

            // left mouse button => dragging
            if( !event.isPrimaryButtonDown())
                return;

            nodeDragContext.mouseAnchorX = event.getSceneX();
            nodeDragContext.mouseAnchorY = event.getSceneY();

            Node node = (Node) event.getSource();

            nodeDragContext.translateAnchorX = node.getTranslateX();
            nodeDragContext.translateAnchorY = node.getTranslateY();

        

    ;

    private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() 
        public void handle(MouseEvent event) 

            // left mouse button => dragging
            if( !event.isPrimaryButtonDown())
                return;

            double scale = canvas.getScale();

            Node node = (Node) event.getSource();

            node.setTranslateX(nodeDragContext.translateAnchorX + (( event.getSceneX() - nodeDragContext.mouseAnchorX) / scale));
            node.setTranslateY(nodeDragContext.translateAnchorY + (( event.getSceneY() - nodeDragContext.mouseAnchorY) / scale));

            event.consume();

        
    ;


/**
 * Listeners for making the scene's canvas draggable and zoomable
 */
class SceneGestures 

    private static final double MAX_SCALE = 10.0d;
    private static final double MIN_SCALE = .1d;

    private DragContext sceneDragContext = new DragContext();

    PannableCanvas canvas;

    public SceneGestures( PannableCanvas canvas) 
        this.canvas = canvas;
    

    public EventHandler<MouseEvent> getOnMousePressedEventHandler() 
        return onMousePressedEventHandler;
    

    public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() 
        return onMouseDraggedEventHandler;
    

    public EventHandler<ScrollEvent> getOnScrollEventHandler() 
        return onScrollEventHandler;
    

    private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() 

        public void handle(MouseEvent event) 

            // right mouse button => panning
            if( !event.isSecondaryButtonDown())
                return;

            sceneDragContext.mouseAnchorX = event.getSceneX();
            sceneDragContext.mouseAnchorY = event.getSceneY();

            sceneDragContext.translateAnchorX = canvas.getTranslateX();
            sceneDragContext.translateAnchorY = canvas.getTranslateY();

        

    ;

    private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() 
        public void handle(MouseEvent event) 

            // right mouse button => panning
            if( !event.isSecondaryButtonDown())
                return;

            canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX);
            canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - sceneDragContext.mouseAnchorY);

            event.consume();
        
    ;

    /**
     * Mouse wheel handler: zoom to pivot point
     */
    private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() 

        @Override
        public void handle(ScrollEvent event) 

            double delta = 1;

            double scale = canvas.getScale(); // currently we only use Y, same value is used for X
            double oldScale = scale;

            if (event.getDeltaY() < 0)
                scale -= delta;
            else
                scale += delta;

            if (scale <= MIN_SCALE) 
                scale = MIN_SCALE;
             else if (scale >= MAX_SCALE) 
                scale = MAX_SCALE;
            

            // pivot value must be untransformed, i. e. without scaling
            canvas.setPivot( 
                    ((event.getSceneX() - canvas.getBoundsInParent().getMinX()) / oldScale),
                    ((event.getSceneY() - canvas.getBoundsInParent().getMinY()) / oldScale)
                    );

            canvas.setScale( scale);

            System.out.println( "new pivot x: " + canvas.scaleTransform.getPivotX() + "/" + canvas.scaleTransform.getPivotY() + ", new scale: " + scale);
            System.out.println( "bounds: " + canvas.getBoundsInParent());       

            event.consume();

        

    ;




/**
 * An application with a zoomable and pannable canvas.
 */
public class ScrollApplication extends Application 
    public static void main(String[] args) 
        launch(args);
    

    @Override
    public void start(Stage stage) 

        Group group = new Group();

        // create canvas
        PannableCanvas canvas = new PannableCanvas();

        // we don't want the canvas on the top/left in this example => just
        // translate it a bit
        canvas.setTranslateX(100);
        canvas.setTranslateY(100);

        // create sample nodes which can be dragged
        NodeGestures nodeGestures = new NodeGestures( canvas);

        Label label1 = new Label("Draggable node 1");
        label1.setTranslateX(10);
        label1.setTranslateY(10);
        label1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        label1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        Label label2 = new Label("Draggable node 2");
        label2.setTranslateX(100);
        label2.setTranslateY(100);
        label2.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        label2.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        Label label3 = new Label("Draggable node 3");
        label3.setTranslateX(200);
        label3.setTranslateY(200);
        label3.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        label3.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        Circle circle1 = new Circle( 300, 300, 50);
        circle1.setStroke(Color.ORANGE);
        circle1.setFill(Color.ORANGE.deriveColor(1, 1, 1, 0.5));
        circle1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        circle1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        Rectangle rect1 = new Rectangle(100,100);
        rect1.setTranslateX(450);
        rect1.setTranslateY(450);
        rect1.setStroke(Color.BLUE);
        rect1.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.5));
        rect1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        rect1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        canvas.getChildren().addAll(label1, label2, label3, circle1, rect1);

        group.getChildren().add(canvas);

        // create scene which can be dragged and zoomed
        Scene scene = new Scene(group, 1024, 768);

        SceneGestures sceneGestures = new SceneGestures(canvas);
        scene.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
        scene.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
        scene.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());

        stage.setScene(scene);
        stage.show();

        canvas.addGrid();

    

支点计算显然有问题,但我不知道它是什么以及如何解决它。

非常感谢!

【问题讨论】:

另见variation。 【参考方案1】:

首先,我建议不要以线性步骤进行缩放,而是通过因素来平滑缩放:

           double delta = 1.2;
           if (event.getDeltaY() < 0)
                scale /= delta;
            else
                scale *= delta;

...为了有点专横,我推荐大括号作为一种很好的风格;-) :

               double delta = 1.2;
               if (event.getDeltaY() < 0) 
                    scale /= delta;
                else 
                    scale *= delta;
               

...并使用鼠标滚动值获得更好的质量:

               double delta = 1.2;
               if (event.getDeltaY() < 0) 
                    scale /= Math.pow(delta, -event.getDeltaY()/20);
                else 
                    scale *= Math.pow(delta, event.getDeltaY()/20);
               

... 终于一样了:

               scale *= Math.pow(1.01, event.getDeltaY());

其次,我建议使用画布平移和缩放属性而不是转换:

public class ZoomApplication extends Application 
    static public class PannableCanvas extends Pane 

        DoubleProperty myScale = new SimpleDoubleProperty(1.0);

        public PannableCanvas() 

            setPrefSize(600, 600);
            setStyle("-fx-background-color: lightgrey; -fx-border-color: blue;");

            // add scale transform
            scaleXProperty().bind(myScale);
            scaleYProperty().bind(myScale);

            // logging
            addEventFilter(MouseEvent.MOUSE_PRESSED, event ->  
                System.out.println( 
                        "canvas event: " + ( ((event.getSceneX() - getBoundsInParent().getMinX()) / getScale()) + ", scale: " + getScale())
                        );
                System.out.println( "canvas bounds: " + getBoundsInParent());   
            );

        

        /**
         * Add a grid to the canvas, send it to back
         */
        public void addGrid() 

            double w = getBoundsInLocal().getWidth();
            double h = getBoundsInLocal().getHeight();

            // add grid
            Canvas grid = new Canvas(w, h);

            // don't catch mouse events
            grid.setMouseTransparent(true);

            GraphicsContext gc = grid.getGraphicsContext2D();

            gc.setStroke(Color.GRAY);
            gc.setLineWidth(1);

            // draw grid lines
            double offset = 50;
            for( double i=offset; i < w; i+=offset) 
                // vertical
                gc.strokeLine( i, 0, i, h);
                // horizontal
                gc.strokeLine( 0, i, w, i);
            

            getChildren().add( grid);

            grid.toBack();
        

        public double getScale() 
            return myScale.get();
        

        /**
         * Set x/y scale
         * @param myScale
         */
        public void setScale( double scale) 
            myScale.set(scale);
        

        /**
         * Set x/y pivot points
         * @param x
         * @param y
         */
        public void setPivot( double x, double y) 
            setTranslateX(getTranslateX()-x);
            setTranslateY(getTranslateY()-y);
        
    


    /**
     * Mouse drag context used for scene and nodes.
     */
    class DragContext 

        double mouseAnchorX;
        double mouseAnchorY;

        double translateAnchorX;
        double translateAnchorY;

    

    /**
     * Listeners for making the nodes draggable via left mouse button. Considers if parent is zoomed.
     */
    class NodeGestures 

        private DragContext nodeDragContext = new DragContext();

        PannableCanvas canvas;

        public NodeGestures( PannableCanvas canvas) 
            this.canvas = canvas;

        

        public EventHandler<MouseEvent> getOnMousePressedEventHandler() 
            return onMousePressedEventHandler;
        

        public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() 
            return onMouseDraggedEventHandler;
        

        private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() 

            public void handle(MouseEvent event) 

                // left mouse button => dragging
                if( !event.isPrimaryButtonDown())
                    return;

                nodeDragContext.mouseAnchorX = event.getSceneX();
                nodeDragContext.mouseAnchorY = event.getSceneY();

                Node node = (Node) event.getSource();

                nodeDragContext.translateAnchorX = node.getTranslateX();
                nodeDragContext.translateAnchorY = node.getTranslateY();

            

        ;

        private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() 
            public void handle(MouseEvent event) 

                // left mouse button => dragging
                if( !event.isPrimaryButtonDown())
                    return;

                double scale = canvas.getScale();

                Node node = (Node) event.getSource();

                node.setTranslateX(nodeDragContext.translateAnchorX + (( event.getSceneX() - nodeDragContext.mouseAnchorX) / scale));
                node.setTranslateY(nodeDragContext.translateAnchorY + (( event.getSceneY() - nodeDragContext.mouseAnchorY) / scale));

                event.consume();

            
        ;
    

    /**
     * Listeners for making the scene's canvas draggable and zoomable
     */
    class SceneGestures 

        private static final double MAX_SCALE = 10.0d;
        private static final double MIN_SCALE = .1d;

        private DragContext sceneDragContext = new DragContext();

        PannableCanvas canvas;

        public SceneGestures( PannableCanvas canvas) 
            this.canvas = canvas;
        

        public EventHandler<MouseEvent> getOnMousePressedEventHandler() 
            return onMousePressedEventHandler;
        

        public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() 
            return onMouseDraggedEventHandler;
        

        public EventHandler<ScrollEvent> getOnScrollEventHandler() 
            return onScrollEventHandler;
        

        private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() 

            public void handle(MouseEvent event) 

                // right mouse button => panning
                if( !event.isSecondaryButtonDown())
                    return;

                sceneDragContext.mouseAnchorX = event.getSceneX();
                sceneDragContext.mouseAnchorY = event.getSceneY();

                sceneDragContext.translateAnchorX = canvas.getTranslateX();
                sceneDragContext.translateAnchorY = canvas.getTranslateY();

            

        ;

        private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() 
            public void handle(MouseEvent event) 

                // right mouse button => panning
                if( !event.isSecondaryButtonDown())
                    return;

                canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX);
                canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - sceneDragContext.mouseAnchorY);

                event.consume();
            
        ;

        /**
         * Mouse wheel handler: zoom to pivot point
         */
        private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() 

            @Override
            public void handle(ScrollEvent event) 


                double scale = canvas.getScale(); // currently we only use Y, same value is used for X
                double oldScale = scale;

                scale *= Math.pow(1.01, event.getDeltaY());

                if (scale <= MIN_SCALE) 
                    scale = MIN_SCALE;
                 else if (scale >= MAX_SCALE) 
                    scale = MAX_SCALE;
                

                double f = (scale / oldScale)-1;

                double dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth()/2 + canvas.getBoundsInParent().getMinX()));
                double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight()/2 + canvas.getBoundsInParent().getMinY()));


                canvas.setScale( scale);
                canvas.setPivot(f*dx, f*dy);

                event.consume();

            

        ;


    

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

    @Override
    public void start(Stage stage) 

        Group group = new Group();

        // create canvas
        PannableCanvas canvas = new PannableCanvas();

        // we don't want the canvas on the top/left in this example => just
        // translate it a bit
        canvas.setTranslateX(100);
        canvas.setTranslateY(100);

        // create sample nodes which can be dragged
        NodeGestures nodeGestures = new NodeGestures( canvas);

        Label label1 = new Label("Draggable node 1");
        label1.setTranslateX(10);
        label1.setTranslateY(10);
        label1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        label1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        Label label2 = new Label("Draggable node 2");
        label2.setTranslateX(100);
        label2.setTranslateY(100);
        label2.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        label2.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        Label label3 = new Label("Draggable node 3");
        label3.setTranslateX(200);
        label3.setTranslateY(200);
        label3.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        label3.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        Circle circle1 = new Circle( 300, 300, 50);
        circle1.setStroke(Color.ORANGE);
        circle1.setFill(Color.ORANGE.deriveColor(1, 1, 1, 0.5));
        circle1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        circle1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        Rectangle rect1 = new Rectangle(100,100);
        rect1.setTranslateX(450);
        rect1.setTranslateY(450);
        rect1.setStroke(Color.BLUE);
        rect1.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.5));
        rect1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        rect1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        canvas.getChildren().addAll(label1, label2, label3, circle1, rect1);

        group.getChildren().add(canvas);

        // create scene which can be dragged and zoomed
        Scene scene = new Scene(group, 1024, 768);

        SceneGestures sceneGestures = new SceneGestures(canvas);
        scene.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
        scene.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
        scene.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());

        stage.setScene(scene);
        stage.show();

        canvas.addGrid();

    

在对缩放进行了一些思考后,我得出结论,这是一个好主意

    编写一个独立的缩放辅助方法来简化缩放功能 以同样的方法也支持捏拉缩放手势

于是我写了如下辅助方法:

/** Allow to zoom/scale any node with pivot at scene (x,y) coordinates.
 * 
 * @param node
 * @param delta
 * @param x
 * @param y
 */
public static void zoom(Node node, double factor, double x, double y) 
    double oldScale = node.getScaleX();
    double scale = oldScale * factor;
    if (scale < 0.05) scale = 0.05;
    if (scale > 50)  scale = 50;
    node.setScaleX(scale);
    node.setScaleY(scale);

    double  f = (scale / oldScale)-1;
    Bounds bounds = node.localToScene(node.getBoundsInLocal());
    double dx = (x - (bounds.getWidth()/2 + bounds.getMinX()));
    double dy = (y - (bounds.getHeight()/2 + bounds.getMinY()));

    node.setTranslateX(node.getTranslateX()-f*dx);
    node.setTranslateY(node.getTranslateY()-f*dy);


public static void zoom(Node node, ScrollEvent event) 
    zoom(node, Math.pow(1.01, event.getDeltaY()), event.getSceneX(), event.getSceneY());

public static void zoom(Node node, ZoomEvent event) 
    zoom(node, event.getZoomFactor(), event.getSceneX(), event.getSceneY());

允许我在任何节点上注册缩放功能,如下所示:

    myView.setOnScroll(event -> GUITools.zoom(myView, event)); // mouse scroll wheel zoom
    myView.setOnZoom(event -> GUITools.zoom(myView, event)); // pinch to zoom

完成了……

【讨论】:

非常感谢您的快速解决方案。它按预期工作:-) 非常感谢您的更新。我一定会用的。 似乎增量在很大程度上取决于鼠标滚轮设置。我有e。 G。非常高分辨率的屏幕,因此鼠标滚轮滚动值很高。当我使用您的代码并缩小时,我立即得到一个 ~100x100 像素的窗格。所以我想必须考虑使用 getDeltaX() 和 getDeltaY() 的乘数或使用固定的增量。 是的,这是可能的......我也会考虑不直接使用普通的鼠标滚轮(滚动 Y),因为它已经具有当前视图的 Y 移动语义(例如使用两个手指滑动以在平板电脑上的 X、Y 方向滚动)...也许 1.003 的基数可能会更好,而不是 1.01,以便在缩放速度上更加保守。【参考方案2】:

我改变了你的 SceneGestures 类,所以现在可以工作了。

class SceneGestures 
    private double oldx;
    private double oldy;
    double ttx=0;
    double tty=0;
    private static final double MAX_SCALE = 10.0d;
    private static final double MIN_SCALE = .1d;

    private DragContext sceneDragContext = new DragContext();

    PannableCanvas canvas;

    public SceneGestures( PannableCanvas canvas) 
        this.canvas = canvas;
    

    public EventHandler<MouseEvent> getOnMousePressedEventHandler() 
        return onMousePressedEventHandler;
    

    public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() 
        return onMouseDraggedEventHandler;
    

    public EventHandler<ScrollEvent> getOnScrollEventHandler() 
        return onScrollEventHandler;
    

    private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() 

        public void handle(MouseEvent event) 

            // right mouse button => panning
            if( !event.isSecondaryButtonDown())
                return;

            sceneDragContext.mouseAnchorX = event.getSceneX();
            sceneDragContext.mouseAnchorY = event.getSceneY();

            sceneDragContext.translateAnchorX = canvas.getTranslateX();
            sceneDragContext.translateAnchorY = canvas.getTranslateY();

        

    ;

    private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() 
        public void handle(MouseEvent event) 

            // right mouse button => panning
            if( !event.isSecondaryButtonDown())
                return;

            canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX);
            canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - sceneDragContext.mouseAnchorY);

            event.consume();
        
    ;

    /**
     * Mouse wheel handler: zoom to pivot point
     */
    private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() 

        @Override
        public void handle(ScrollEvent event) 

            double delta = 1;

            double scale = canvas.getScale(); // currently we only use Y, same value is used for X
            double oldScale = scale;

            if (event.getDeltaY() < 0)
                scale -= delta;
            else
                scale += delta;

            if (scale <= MIN_SCALE) 
                scale = MIN_SCALE;
             else if (scale >= MAX_SCALE) 
                scale = MAX_SCALE;
            



            if (oldx==0)
             ttx=event.getSceneX() ;  
             tty=event.getSceneY() ;
            else
            if (oldx!=event.getSceneX())
            ttx=((event.getSceneX()+oldx)/2) ;    
            
            if (oldy!=event.getSceneY())
            tty=((event.getSceneY()+oldy)/2);    
            
            
            // pivot value must be untransformed, i. e. without scaling
            canvas.setPivot( 
                    ((ttx- canvas.getBoundsInParent().getMinX()) / oldScale),
                    ((tty- canvas.getBoundsInParent().getMinY()) / oldScale)
                    );
         //    if (oldx==0)
            oldx=event.getSceneX();
            oldy=event.getSceneY();
         //   
            //try 
             //   Robot rbt=new Robot();
             //   rbt.mouseMove(512, 384);
            // catch (AWTException ex) 
            //    System.out.println(ex.getMessage());
           // 

             canvas.setScale( scale);


            System.out.println( "new pivot x: " + canvas.scaleTransform.getPivotX() + "/" + canvas.scaleTransform.getPivotY() + ", new scale: " + scale);
            System.out.println( "bounds: " + canvas.getBoundsInParent());       
            System.out.println( "old: " + oldx+"  "+oldy);  
            System.out.println( "tt: " + ttx+"  "+tty);  
            event.consume();

        

    ;



【讨论】:

放大到圆形、平移到矩形并继续放大时不起作用。Jens-Peter 的解决方案有效。但我仍然要感谢你的努力:-)【参考方案3】:

在 FX8 中你可以做到

final Affine accumulatedScales = new Affine();  
chart.getTransforms().add(accumulatedScales);  

chart.setOnScroll(new EventHandler<ScrollEvent>()   
    @Override public void handle(ScrollEvent event)   
        accumulatedScales.appendScale(scaleFactor, scaleFactor, event.getX(), event.getY());  
      
);

你就完成了

【讨论】:

它不起作用。当我G。将鼠标光标悬停在圆心上,当我使用滚轮时,圆会移动。【参考方案4】:

通过这种方法,您可以通过变换、旋转的节点放大位置。

    public static void zoom(Node node, double factor, ScrollEvent event) 

            double oldScale = node.getScaleX();
            double scale = oldScale*factor;
            if (scale < MIN_ZOOM) scale = MIN_ZOOM;
            if (scale > MAX_ZOOM)  scale = MAX_ZOOM;

            double x = event.getX();
            double y = event.getY();

            Point2D p0 = node.localToScene(x, y);

            node.setScaleX(scale);
            node.setScaleY(scale);

            Point2D p1 = node.localToScene(x, y);

            double deltaX = p1.getX() - p0.getX();
            double deltaY = p1.getY() - p0.getY();

            node.setTranslateX(node.getTranslateX() - deltaX);
            node.setTranslateY(node.getTranslateY() - deltaY);

        

【讨论】:

以上是关于在已缩放节点中的枢轴点处缩放的主要内容,如果未能解决你的问题,请参考以下文章

Android围绕动态枢轴旋转视图

aws 中的 rabbitmq 自动缩放集群:管理缩放事件

Android 如何使用 BitmapFactory 选项缩放图像

AWS 中的 Kubernetes 自动缩放

使用 CSS3 进行缩放

d3 v7 中的简单平移和缩放