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

时间:2015-09-20 14:50:38

标签: java canvas javafx mouse

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

通过一些调试,看起来你移动的越多,就会得到这种“不精确”的感觉。在每个拖动事件之间,鼠标位置越来越不同步。最终结果是它来回翻转,看起来非常糟糕。

正在使用的代码:

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

Controller.java

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个像素),即使你只是移动它一个像素。

然后它似乎试图在你下次移动时自行修复,这会让它看起来很难看。

我之所以感到困惑的原因是因为有时候它很有效,然后在你继续拖动时精度就会下降。

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

1 个答案:

答案 0 :(得分:2)

您使用MouseEvent.getX()MouseEvent.getY()衡量的坐标是相对于发生事件的节点:即相对于您的Canvas。然后,您移动画布后,旧坐标现在不正确(因为它们相对于旧位置而不是新位置)。因此,deltaXdeltaY的值不正确。

要解决此问题,只需测量相对于“固定”的内容,例如: Scene。您可以使用MouseEvent.getSceneX()MouseEvent.getSceneY()来完成此操作。

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.getSceneX();
                mouseY = e.getSceneY();
            }
        });

        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.getSceneX();
                double newMouseY = e.getSceneY();
                double deltaX = newMouseX - mouseX;
                double deltaY = newMouseY - mouseY;
                canvas.setLayoutX(canvas.getLayoutX() + deltaX);
                canvas.setLayoutY(canvas.getLayoutY() + deltaY);
                mouseX = newMouseX;
                mouseY = newMouseY;
                // Why is this happening?
                if (Math.abs(deltaX) > 4 || Math.abs(deltaY) > 4)
                    System.out.println(deltaX + " " + deltaY);
            }
        });
    }
}

另一种方法(至少对我来说不那么直观)是使用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);
            }
        });
    }
}