gpt4 book ai didi

javafx - 在已经缩放的节点中的枢轴点处缩放

转载 作者:行者123 更新时间:2023-12-04 01:56:35 25 4
gpt4 key购买 nike

我正在尝试创建一个带有可缩放/可平移 Canvas 的应用程序。

特点 :

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

  • 只要您以比例 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();

    }
    }

    枢轴点计算显然有问题,但我无法弄清楚它是什么以及如何修复它。

    非常感谢!

    enter image description here

    最佳答案

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

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

    其次,我建议使用 Canvas 平移和缩放属性而不是转换:
    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

    并做了...

    关于javafx - 在已经缩放的节点中的枢轴点处缩放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27356577/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com