我正在测试键处理程序,我遇到了一个问题。
在最简单的形式中,我有以下代码:mainScene.setOnKeyPressed( event -> {
System.out.println("Handler called for: " + event.getCode());
});
如预期的那样,当按下一个键时,它打印出相关的代码。
问题是,如果我同时按住两个键,只有最后按下的键产生恒定的事件。我希望能够将按下的键添加到队列中,以便在其他地方处理,但只有最后按下的键才会添加到队列中。
有办法改变这种行为吗?
我能找到的唯一解决方法是使用地图来记录代码,并设置一个单独的按下和释放处理程序来从地图中添加/删除代码。这是可行的,但需要不断轮询我可能需要响应的每个键,而不是仅仅检查按下的键队列是否为空。
我怀疑JVM正在从操作系统接收按下的键事件,因此按住两个键时的重复键行为是在操作系统级别确定的。
要管理自己的按键重复,您可以使用具有无限循环计数的时间轴;按下该键时启动时间轴,释放该键时停止时间轴。您可能需要在Map<KeyCode, Timeline>
中管理这些密钥以处理多个密钥。让时间轴调用一个方法并传递键代码来集中处理按键:这将避免轮询的需要。
SSCCE:
import java.util.HashMap;
import java.util.Map;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class MultiRepeatKey extends Application {
@Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new Pane(), 400, 400);
Map<KeyCode, Timeline> keyRepeats = new HashMap<>();
Duration keyPressDelay = Duration.millis(200);
scene.setOnKeyPressed(e -> {
if (! keyRepeats.containsKey(e.getCode())) {
Timeline repeat = new Timeline(new KeyFrame(Duration.ZERO, event -> processKey(e.getCode())),
new KeyFrame(keyPressDelay));
repeat.setCycleCount(Animation.INDEFINITE);
repeat.play();
keyRepeats.put(e.getCode(), repeat);
}
});
scene.setOnKeyReleased(e -> {
if (keyRepeats.containsKey(e.getCode())) {
Timeline repeat = keyRepeats.get(e.getCode());
repeat.stop();
keyRepeats.remove(e.getCode());
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
private void processKey(KeyCode code) {
System.out.println(code.getName());
}
public static void main(String[] args) {
launch(args);
}
}
根据您的用例,另一个可能对您有意义的选择是仅保留一个Map
,从键到您想要的功能的一些表示,然后保留这些功能的实现的Set
。然后使用AnimationTimer
根据按下的键来更新UI。(AnimationTimer
在每帧渲染时执行handle
方法;传入的参数是一个以纳秒为单位的时间戳。
显然,如果你有很多映射,你会在其他地方定义映射,但这里是思路:
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.DoubleFunction;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class MultiRepeatKey extends Application {
@Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(20, 20, 50, 50);
rect.setFill(Color.CORNFLOWERBLUE);
Pane pane = new Pane(rect);
Set<DoubleFunction<Point2D>> motions = new HashSet<>();
Map<KeyCode, DoubleFunction<Point2D>> keyMappings = new HashMap<>();
keyMappings.put(KeyCode.UP, delta -> new Point2D(0, -delta));
keyMappings.put(KeyCode.DOWN, delta -> new Point2D(0, delta));
keyMappings.put(KeyCode.LEFT, delta -> new Point2D(-delta, 0));
keyMappings.put(KeyCode.RIGHT, delta -> new Point2D(delta, 0));
double speed = 150.0 ; // pixels / second
AnimationTimer anim = new AnimationTimer() {
private long lastUpdate = 0 ;
@Override
public void handle(long now) {
if (lastUpdate > 0) {
double elapsedSeconds = (now - lastUpdate) / 1_000_000_000.0 ;
double delta = speed * elapsedSeconds ;
Point2D loc = motions.stream()
.map(m -> m.apply(delta))
.reduce(new Point2D(rect.getX(), rect.getY()), Point2D::add);
loc = clamp(loc, 0, 0, pane.getWidth() - rect.getWidth(), pane.getHeight() - rect.getHeight());
rect.setX(loc.getX());
rect.setY(loc.getY());
}
lastUpdate = now ;
}
};
anim.start();
Scene scene = new Scene(pane, 400, 400);
scene.setOnKeyPressed(e -> motions.add(keyMappings.get(e.getCode())));
scene.setOnKeyReleased(e -> motions.remove(keyMappings.get(e.getCode())));
primaryStage.setScene(scene);
primaryStage.show();
}
private Point2D clamp(Point2D p, double minX, double minY, double maxX, double maxY) {
if (p.getX() < minX) {
p = new Point2D(minX, p.getY());
} else if (p.getX() > maxX) {
p = new Point2D(maxX, p.getY());
}
if (p.getY() < minY) {
p = new Point2D(p.getX(), minY);
} else if (p.getY() > maxY) {
p = new Point2D(p.getX(), maxY);
}
return p ;
}
public static void main(String[] args) {
launch(args);
}
}