JavaFX MouseEvent 定位精度随时间降低,导致节点移动抖动
JavaFX MouseEvent location accuracy degrades over time, results in node movement jittering
我不明白为什么会这样。目前我正在尝试通过右键单击一个节点 (Canvas) 来拖动它,按住右键并移动鼠标。一开始它很流畅,但随后开始出现这种奇怪的抖动。这仅在我按住鼠标按钮时发生。如果你释放它,它就会恢复正常(但很快就会再次紧张)。
通过一些调试,您移动它的次数越多,您就会在每次拖动事件之间获得此 'imprecision',鼠标位置越来越不同步。最后的结果就是来回拖,看起来很糟糕
正在使用的代码:
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) 右键单击 canvas 并稍微移动它(比如每秒只移动一个像素)
3) 移动得快一点,以便调试打印
4) 当你回到缓慢移动时,由于某种原因你会看到它来回跳跃(比如新旧位置之间的增量移动大于 5 像素)即使你只是移动它一个像素。
然后它似乎会在您下次移动时尝试自行修复,这使它看起来很丑陋。
我感到困惑的原因是,有时它工作得很好,然后当你继续拖动时准确度就会下降。
我是不是编码有误,或者这是一个潜在的错误?
您使用 MouseEvent.getX()
和 MouseEvent.getY()
测量的坐标是相对于事件发生的节点:即相对于您的 Canvas
。由于您随后移动了 canvas,旧坐标现在不正确(因为它们是相对于旧位置,而不是新位置)。因此,deltaX
和 deltaY
的值不正确。
要解决此问题,只需相对于 "fixed" 的某物进行测量,例如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()
但不更新鼠标的 "last coordinates"。考虑这一点的方式是,当节点被拖动时,您希望它不相对于鼠标移动 - 换句话说,您希望节点移动以便鼠标的坐标相对于它保持固定.这个版本看起来像:
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);
}
});
}
}
我不明白为什么会这样。目前我正在尝试通过右键单击一个节点 (Canvas) 来拖动它,按住右键并移动鼠标。一开始它很流畅,但随后开始出现这种奇怪的抖动。这仅在我按住鼠标按钮时发生。如果你释放它,它就会恢复正常(但很快就会再次紧张)。
通过一些调试,您移动它的次数越多,您就会在每次拖动事件之间获得此 'imprecision',鼠标位置越来越不同步。最后的结果就是来回拖,看起来很糟糕
正在使用的代码:
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) 右键单击 canvas 并稍微移动它(比如每秒只移动一个像素)
3) 移动得快一点,以便调试打印
4) 当你回到缓慢移动时,由于某种原因你会看到它来回跳跃(比如新旧位置之间的增量移动大于 5 像素)即使你只是移动它一个像素。
然后它似乎会在您下次移动时尝试自行修复,这使它看起来很丑陋。
我感到困惑的原因是,有时它工作得很好,然后当你继续拖动时准确度就会下降。
我是不是编码有误,或者这是一个潜在的错误?
您使用 MouseEvent.getX()
和 MouseEvent.getY()
测量的坐标是相对于事件发生的节点:即相对于您的 Canvas
。由于您随后移动了 canvas,旧坐标现在不正确(因为它们是相对于旧位置,而不是新位置)。因此,deltaX
和 deltaY
的值不正确。
要解决此问题,只需相对于 "fixed" 的某物进行测量,例如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()
但不更新鼠标的 "last coordinates"。考虑这一点的方式是,当节点被拖动时,您希望它不相对于鼠标移动 - 换句话说,您希望节点移动以便鼠标的坐标相对于它保持固定.这个版本看起来像:
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);
}
});
}
}