利用key bindings API,它会解决这个问题,以及所有其他与KeyListener 相关的问题,而不会因为它“有时”起作用而费尽心思。
Swing 是单线程的。这意味着您应该小心从事件调度线程的上下文之外(直接或间接)更新 UI 的状态,这还包括 UI 可能依赖的任何状态。
查看Concurrency in Swing了解更多详情。
如果没有更多细节,最简单的解决方案之一是使用 Swing Timer 作为主要的“tick”动作。每次它“打勾”时,您都会检查输入的状态并更新模型的状态并触发重绘以更新 UI
例如...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
enum Input {
UP, DOWN, LEFT, RIGHT
}
private Set<Input> input = new HashSet<>();
public TestPane() {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
am.put("Left.pressed", new InputAction(Input.LEFT, input, false));
am.put("Left.released", new InputAction(Input.LEFT, input, true));
am.put("Right.pressed", new InputAction(Input.RIGHT, input, false));
am.put("Right.released", new InputAction(Input.RIGHT, input, true));
am.put("Down.pressed", new InputAction(Input.DOWN, input, false));
am.put("Down.released", new InputAction(Input.DOWN, input, true));
am.put("Up.pressed", new InputAction(Input.UP, input, false));
am.put("Up.released", new InputAction(Input.UP, input, true));
input.add(Input.UP);
input.add(Input.DOWN);
input.add(Input.LEFT);
input.add(Input.RIGHT);
Timer timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Check what's currently been pressed
// and update the state accordingly...
if (input.contains(Input.UP)) {
//..
}
if (input.contains(Input.DOWN)) {
//..
}
if (input.contains(Input.LEFT)) {
//..
}
if (input.contains(Input.RIGHT)) {
//..
}
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int midX = getWidth() / 2;
int midY = getHeight() / 2;
FontMetrics fm = g2d.getFontMetrics();
int spaceWidth = fm.stringWidth("M");
int spaceHeight = fm.getHeight();
if (input.contains(Input.UP)) {
String text = "UP";
g2d.drawString(text,
midX - (fm.stringWidth(text)) / 2,
(midY - (spaceHeight * 2) + fm.getAscent()));
}
if (input.contains(Input.DOWN)) {
String text = "DOWN";
g2d.drawString(text,
midX - (fm.stringWidth(text)) / 2,
(midY + spaceHeight + fm.getAscent()));
}
if (input.contains(Input.LEFT)) {
String text = "LEFT";
g2d.drawString(text,
(midX - spaceWidth - fm.stringWidth(text)),
(midY + (fm.getAscent() / 2)));
}
if (input.contains(Input.RIGHT)) {
String text = "RIGHT";
g2d.drawString(text,
(midX + spaceWidth),
(midY + (fm.getAscent() / 2)));
}
g2d.dispose();
}
public class InputAction extends AbstractAction {
private Input input;
private Set<Input> inputSet;
private boolean onRelease;
public InputAction(Input input, Set<Input> inputSet, boolean onRelease) {
this.input = input;
this.inputSet = inputSet;
this.onRelease = onRelease;
}
@Override
public void actionPerformed(ActionEvent e) {
if (onRelease) {
inputSet.remove(input);
} else {
inputSet.add(input);
}
}
}
}
}
这是一个基本示例,直接在paintComponent 方法中打印键输入的状态。更现实的实现是使用Timer 的ActionListener 来检查input 状态并相应地更改所需的模型,这在示例中有所暗示。
如果您需要更直接的控制,那么您需要避免使用 Swing 绘画系统并自行控制。为此,您需要使用BufferStrategy。这是一个更复杂的解决方案,但它也更强大,因为您可以完全控制 UI 的更新时间