我想出了以下 GUI。
我想解释两个重要的原则。首先是创建 GUI 是一个独立于更新 GUI 的过程。二是游戏过程是一个状态机。游戏处于六个不同的状态。这是我为牢记州而写的内容。
事件顺序
- 左键单击
- 等待 2 - 4 秒以显示圆圈。
- 捕获开始时间
- 左键单击
- 捕获结束时间。
- 计算并显示反应时间。
- 重复 1 - 6。
所以,对于 GUI,我创建了一个 JFrame 和三个 JPanels;一个上部JPanel,一个绘图JPanel,和一个按钮JPanel。
我通过调用 SwingUtilities invokeLater 方法启动了 Swing 应用程序。此方法确保在Event Dispatch Thread 上创建和执行 Swing 组件。
JFrame 有一个默认的BorderLayout,我用来放置三个JPanels。 JFrame 方法调用必须按特定顺序执行。这是我用于所有 Swing 应用程序的顺序。
上面的JPanel 包含指令和反应时间显示。 JTextArea 非常适合显示说明。我使用FlowLayout 将JTextArea 放在内部JPanel 中,我使用BorderLayout 将其放在上部JPanel 中。像这样嵌套布局是一种以逻辑方式组织 Swing 组件的好方法。
我把反应时间 Swing 组件放在另一个内部的JPanel,我把它放在上面的JPanel。
我创建了一个绘图JPanel,这样我就不必为图像而烦恼了。
按钮JPanel 持有提交JButton。
我创建了两个控制器类。一个控制器类ButtonListener 响应JButton 左键单击。另一个控制器类 TimerListener 为绘制圆创建了延迟。
ButtonListener 状态变量允许我使用相同的ActionListener 提供不同的功能。如果您愿意,可以编写单独的 ActionListener 类,每个函数一个。
通过将我的代码分为视图类和控制器类,我可以将我的关注点分开并一次专注于应用程序的一个部分。
这是完整的可运行代码。我将所有类都设为内部类,这样我就可以将这段代码作为一个块发布。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ReactionTimeGame implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new ReactionTimeGame());
}
private long reactionTime;
private DrawingPanel drawingPanel;
private JTextField reactionTimeField;
public ReactionTimeGame() {
this.reactionTime = 0L;
}
@Override
public void run() {
JFrame frame = new JFrame("Reaction Time Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createUpperPanel(), BorderLayout.BEFORE_FIRST_LINE);
this.drawingPanel = new DrawingPanel();
frame.add(drawingPanel, BorderLayout.CENTER);
frame.add(createButtonPanel(), BorderLayout.AFTER_LAST_LINE);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createUpperPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JPanel innerPanel = new JPanel(new FlowLayout());
String instructions = "This game will test your reaction time. To play "
+ "the game, left-click on the Submit button. After a random time "
+ "from 2 - 4 seconds, a circle will appear. Left-click the "
+ "Submit button again. Your reaction time will be displayed "
+ "above where the circle was.\n\n"
+ "Left-click the Submit button to start each round of the game.";
JTextArea textArea = new JTextArea(7, 40);
textArea.setEditable(false);
textArea.setText(instructions);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
innerPanel.add(textArea);
panel.add(innerPanel, BorderLayout.BEFORE_FIRST_LINE);
innerPanel = new JPanel(new FlowLayout());
JLabel label = new JLabel("Reaction Time:");
innerPanel.add(label);
reactionTimeField = new JTextField(5);
reactionTimeField.setEditable(false);
updateReactionTime();
innerPanel.add(reactionTimeField);
label = new JLabel("seconds");
innerPanel.add(label);
panel.add(innerPanel, BorderLayout.AFTER_LAST_LINE);
return panel;
}
private JPanel createButtonPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JButton button = new JButton("Submit");
button.addActionListener(new ButtonListener());
panel.add(button);
return panel;
}
public void setReactionTime(long reactionTime) {
this.reactionTime = reactionTime;
}
public void drawCircle() {
drawingPanel.setDrawCircle(true);
drawingPanel.repaint();
}
public void eraseCircle() {
drawingPanel.setDrawCircle(false);
drawingPanel.repaint();
}
public void updateReactionTime() {
double time = 0.001 * reactionTime;
reactionTimeField.setText(String.format("%.3f", time));
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private boolean drawCircle;
public DrawingPanel() {
this.drawCircle = false;
this.setPreferredSize(new Dimension(300, 300));
}
public void setDrawCircle(boolean drawCircle) {
this.drawCircle = drawCircle;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (drawCircle) {
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = Math.min(getWidth(), getHeight()) * 9 / 20;
int diameter = radius + radius;
g.setColor(Color.MAGENTA);
g.fillOval(centerX - radius, centerY - radius, diameter, diameter);
}
}
}
public class ButtonListener implements ActionListener {
private int state;
private long startTime;
private final Random random;
private Timer timer;
public ButtonListener() {
this.state = 1;
this.random = new Random();
}
@Override
public void actionPerformed(ActionEvent event) {
switch (state) {
case 1:
int delay = random.nextInt(2000) + 2000;
timer = new Timer(delay, new TimerListener(this));
timer.start();
state = 2;
break;
case 2:
setEndTime(System.currentTimeMillis());
eraseCircle();
state = 1;
break;
}
}
public int getState() {
return state;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public void setEndTime(long endTime) {
long elapsedTime = endTime - startTime;
setReactionTime(elapsedTime);
updateReactionTime();
}
}
public class TimerListener implements ActionListener {
private final ButtonListener listener;
public TimerListener(ButtonListener listener) {
this.listener = listener;
}
@Override
public void actionPerformed(ActionEvent event) {
Timer timer = (Timer) event.getSource();
timer.stop();
if (listener.getState() == 2) {
listener.setStartTime(System.currentTimeMillis());
drawCircle();
}
}
}
}