JavaFX MouseeEvent的定位精度会随着时间的推移而降低,导致节点移动抖动

JavaFX MouseeEvent的定位精度会随着时间的推移而降低,导致节点移动抖动,java,canvas,javafx,mouse,Java,Canvas,Javafx,Mouse,我不明白为什么会这样。目前,我正试图通过右键单击、按住右键单击并移动鼠标来拖动节点(画布)。起初它是平滑的,但后来它开始有这种奇怪的抖动。这只在我按住鼠标按钮时发生。如果你释放它,它就会恢复正常(但很快又会变得紧张) 通过一些调试,您移动它的次数越多,在鼠标位置越来越不同步的每个拖动事件之间,您就会获得这种“不精确性”。最终的结果是它会前后摇摆,这看起来很糟糕 正在使用的代码: Main.java import javafx.application.Application; import jav

我不明白为什么会这样。目前,我正试图通过右键单击、按住右键单击并移动鼠标来拖动节点(画布)。起初它是平滑的,但后来它开始有这种奇怪的抖动。这只在我按住鼠标按钮时发生。如果你释放它,它就会恢复正常(但很快又会变得紧张)

通过一些调试,您移动它的次数越多,在鼠标位置越来越不同步的每个拖动事件之间,您就会获得这种“不精确性”。最终的结果是它会前后摇摆,这看起来很糟糕

正在使用的代码:

Main.java

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        try {
            FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("CanvasPane.fxml"));
            Pane root = fxmlLoader.load();
            Scene scene = new Scene(root, 512, 512);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;

public class Controller {

    @FXML
    private Pane rootPane;

    @FXML
    private Canvas canvas;

    private double mouseX;

    private double mouseY;

    @FXML
    private void initialize() {
        GraphicsContext gc = canvas.getGraphicsContext2D();

        // Set the writing pen.
        gc.setLineCap(StrokeLineCap.ROUND);
        gc.setLineJoin(StrokeLineJoin.ROUND);
        gc.setLineWidth(1);
        gc.setStroke(Color.BLACK);

        // Set the background to be transparent.
        gc.setFill(Color.BLUE);
        gc.fillRect(0, 0, 256, 256);

        // Handle moving the canvas.
        canvas.setOnMousePressed(e -> {
            if (e.getButton() == MouseButton.PRIMARY) {
                gc.moveTo(e.getX(), e.getY());
            } else if (e.getButton() == MouseButton.SECONDARY) {
                mouseX = e.getX();
                mouseY = e.getY();
            }
        });

        canvas.setOnMouseDragged(e -> {
            if (e.getButton() == MouseButton.PRIMARY) {
                gc.lineTo(e.getX(), e.getY());
                gc.stroke();
            } else if (e.getButton() == MouseButton.SECONDARY) {
                double newMouseX = e.getX();
                double newMouseY = e.getY();
                double deltaX = newMouseX - mouseX;
                double deltaY = newMouseY - mouseY;
                canvas.setLayoutX(canvas.getLayoutX() + deltaX);
                canvas.setLayoutY(canvas.getLayoutY() + deltaY);
                mouseX = newMouseX;
                mouseY = newMouseY;
                // Debug: Why is this happening?
                if (Math.abs(deltaX) > 4 || Math.abs(deltaY) > 4)
                    System.out.println(deltaX + " " + deltaY);
            }
        });
    }
}
Controller.java

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        try {
            FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("CanvasPane.fxml"));
            Pane root = fxmlLoader.load();
            Scene scene = new Scene(root, 512, 512);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;

public class Controller {

    @FXML
    private Pane rootPane;

    @FXML
    private Canvas canvas;

    private double mouseX;

    private double mouseY;

    @FXML
    private void initialize() {
        GraphicsContext gc = canvas.getGraphicsContext2D();

        // Set the writing pen.
        gc.setLineCap(StrokeLineCap.ROUND);
        gc.setLineJoin(StrokeLineJoin.ROUND);
        gc.setLineWidth(1);
        gc.setStroke(Color.BLACK);

        // Set the background to be transparent.
        gc.setFill(Color.BLUE);
        gc.fillRect(0, 0, 256, 256);

        // Handle moving the canvas.
        canvas.setOnMousePressed(e -> {
            if (e.getButton() == MouseButton.PRIMARY) {
                gc.moveTo(e.getX(), e.getY());
            } else if (e.getButton() == MouseButton.SECONDARY) {
                mouseX = e.getX();
                mouseY = e.getY();
            }
        });

        canvas.setOnMouseDragged(e -> {
            if (e.getButton() == MouseButton.PRIMARY) {
                gc.lineTo(e.getX(), e.getY());
                gc.stroke();
            } else if (e.getButton() == MouseButton.SECONDARY) {
                double newMouseX = e.getX();
                double newMouseY = e.getY();
                double deltaX = newMouseX - mouseX;
                double deltaY = newMouseY - mouseY;
                canvas.setLayoutX(canvas.getLayoutX() + deltaX);
                canvas.setLayoutY(canvas.getLayoutY() + deltaY);
                mouseX = newMouseX;
                mouseY = newMouseY;
                // Debug: Why is this happening?
                if (Math.abs(deltaX) > 4 || Math.abs(deltaY) > 4)
                    System.out.println(deltaX + " " + deltaY);
            }
        });
    }
}
CanvasPane.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.canvas.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.Pane?>

<Pane fx:id="rootPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="0.0" minWidth="0.0" prefHeight="512.0" prefWidth="512.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="your-controller-here">
   <children>
      <Canvas fx:id="canvas" height="256.0" layoutX="122.0" layoutY="107.0" width="256.0" />
   </children>
</Pane>

使用说明

1) 运行应用程序

2) 在画布上单击鼠标右键并稍微移动(例如每秒仅移动一个像素)

3) 将其移动快一点,以便调试打印

4) 当你回到慢速移动时,由于某种原因,你会看到它来回跳跃(就像新旧位置之间的增量移动大于5像素),即使你只移动了一个像素

然后,它似乎试图在你下次移动时修复自己,这给了它一个丑陋的神经过敏的外观


我感到困惑的原因是,有时它工作得很好,但当你继续拖动时,精确度就会下降


我是否编写了错误的代码,或者这是一个潜在的错误?

使用
MouseEvent.getX()
MouseEvent.getY()
测量的坐标与事件发生的节点相关:即相对于
画布。由于随后移动画布,旧坐标现在不正确(因为它们是相对于旧位置的,而不是相对于新位置的)。因此,
deltaX
deltaY
的值不正确

要解决此问题,只需相对于“固定”的对象进行测量,例如
场景
。您可以使用
MouseEvent.getSceneX()
MouseEvent.getSceneY()
来实现这一点

另一种方法(至少对我来说不是很直观)是使用
MouseEvent.getX()
MouseEvent.getY()
,但不更新鼠标的“最后一个坐标”。考虑这一点的方法是,当节点被拖动时,您希望它不会相对于鼠标移动——换句话说,您希望节点移动,以便鼠标的坐标相对于鼠标保持固定。此版本看起来像:

import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;

public class CanvasDragController {

    @FXML
    private Pane rootPane;

    @FXML
    private Canvas canvas;

    private double mouseX;

    private double mouseY;

    @FXML
    private void initialize() {
        GraphicsContext gc = canvas.getGraphicsContext2D();

        // Set the writing pen.
        gc.setLineCap(StrokeLineCap.ROUND);
        gc.setLineJoin(StrokeLineJoin.ROUND);
        gc.setLineWidth(1);
        gc.setStroke(Color.BLACK);

        // Set the background to be transparent.
        gc.setFill(Color.BLUE);
        gc.fillRect(0, 0, 256, 256);

        // Handle moving the canvas.
        canvas.setOnMousePressed(e -> {
            if (e.getButton() == MouseButton.PRIMARY) {
                gc.moveTo(e.getX(), e.getY());
            } else if (e.getButton() == MouseButton.SECONDARY) {
                mouseX = e.getX();
                mouseY = e.getY();
            }
        });

        canvas.setOnMouseDragged(e -> {
            if (e.getButton() == MouseButton.PRIMARY) {
                gc.lineTo(e.getX(), e.getY());
                gc.stroke();
            } else if (e.getButton() == MouseButton.SECONDARY) {
                double newMouseX = e.getX();
                double newMouseY = e.getY();
                double deltaX = newMouseX - mouseX;
                double deltaY = newMouseY - mouseY;
                canvas.setLayoutX(canvas.getLayoutX() + deltaX);
                canvas.setLayoutY(canvas.getLayoutY() + deltaY);
                // Why is this happening?
                if (Math.abs(deltaX) > 4 || Math.abs(deltaY) > 4)
                    System.out.println(deltaX + " " + deltaY);
            }
        });
    }
}