尝试为具有简单计时器(带有开始/重置和停止键绑定)的游戏创建覆盖。使用自定义进度条来绘制计时器进度和计时器中的其他一些关键点。
由于某种未知的原因,启动计时器、计时器滴答和停止计时器会导致一些 UI 重影问题,调整窗口大小会暂时清除它,直到下一个导致重影的操作为止。
造成这种重影的原因是什么以及如何防止它?
截图:
最小复制:
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Timer;
import java.util.TimerTask;
@Slf4j
public class SmudgeTimerPanel extends JPanel {
private static final int TIMER_SPIRIT = 180; // seconds
private static final int TIMER_DEMON = 60; // seconds
private static final int TIMER_OTHER = 90; // seconds
private final CustomProgressBar progressBar;
private final JLabel timeRemainingLabel;
private final JLabel statusLabel;
private final JLabel ghostLabel;
private Timer timer;
private int remainingTime;
private boolean isRunning;
public SmudgeTimerPanel() {
setLayout(new GridBagLayout());
setBackground(new Color(50, 50, 50, 150)); // Transparent gray background
progressBar = new CustomProgressBar(0, TIMER_SPIRIT);
timeRemainingLabel = new JLabel("3:00");
timeRemainingLabel.setForeground(Color.WHITE);
timeRemainingLabel.setBackground(new Color(0, 0, 0, 0));
timeRemainingLabel.setFont(timeRemainingLabel.getFont().deriveFont(24f));
statusLabel = new JLabel();
statusLabel.setOpaque(true);
statusLabel.setBackground(Color.RED);
statusLabel.setPreferredSize(new Dimension(10, 10));
ghostLabel = new JLabel("None");
ghostLabel.setForeground(Color.WHITE);
ghostLabel.setFont(ghostLabel.getFont().deriveFont(24f));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(10, 10, 10, 10);
add(progressBar, gbc);
gbc.gridy = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.CENTER;
JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
statusPanel.setOpaque(false);
statusPanel.add(statusLabel);
statusPanel.add(timeRemainingLabel);
add(statusPanel, gbc);
gbc.gridy = 2;
gbc.gridx = 0;
gbc.fill = GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.CENTER;
add(ghostLabel, gbc);
gbc.gridy = 3;
gbc.fill = GridBagConstraints.HORIZONTAL;
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonPanel.setOpaque(false);
JButton startResetButton = new JButton("Start/Reset Timer");
startResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
resetTimer(TIMER_SPIRIT);
}
});
JButton stopButton = new JButton("Stop Timer");
stopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
stopTimer();
}
});
buttonPanel.add(startResetButton);
buttonPanel.add(stopButton);
add(buttonPanel, gbc);
}
public void resetTimer(int timerDuration) {
if (timer != null) {
timer.cancel();
}
remainingTime = timerDuration;
isRunning = true;
updateGhostLabel();
updateTimeDisplay();
progressBar.setMaximum(timerDuration);
progressBar.setValue(timerDuration);
statusLabel.setBackground(Color.GREEN);
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
SwingUtilities.invokeLater(() -> {
if (remainingTime > 0) {
remainingTime--;
progressBar.setValue(remainingTime);
updateTimeDisplay();
updateGhostLabel();
} else {
stopTimer();
}
});
}
}, 0, 1000);
}
public void stopTimer() {
if (timer != null) {
timer.cancel();
}
isRunning = false;
statusLabel.setBackground(Color.RED);
}
public void startOrResetTimer(String ghostType) {
resetTimer(TIMER_SPIRIT);
}
private void updateTimeDisplay() {
int minutes = remainingTime / 60;
int seconds = remainingTime % 60;
timeRemainingLabel.setText(String.format("%d:%02d", minutes, seconds));
timeRemainingLabel.setBackground(new Color(0, 0, 0, 0));
}
private void updateGhostLabel() {
if (remainingTime > TIMER_OTHER) {
ghostLabel.setText("Spirit");
} else if (remainingTime > TIMER_DEMON) {
ghostLabel.setText("All Others");
} else {
ghostLabel.setText("Demon");
}
}
public boolean isTimerRunning() {
return isRunning;
}
@Slf4j
private static class CustomProgressBar extends JComponent {
private int value;
private int maximum;
public CustomProgressBar(int min, int max) {
this.value = min;
this.maximum = max;
setPreferredSize(new Dimension(300, 20));
setMinimumSize(new Dimension(300, 20));
}
public void setValue(int value) {
this.value = Math.min(value, maximum);
repaint();
}
public void setMaximum(int maximum) {
this.maximum = maximum;
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int width = getWidth();
int height = getHeight();
log.info("Width: {}, Height: {}", width, height);
// Draw background
g2d.setColor(Color.GRAY);
g2d.fillRect(0, 0, width, height);
// Draw progress
int progressWidth = (int) ((value / (double) maximum) * width);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, progressWidth, height);
// Draw tick marks
g2d.setColor(Color.BLACK);
int demonMark = (int) (((TIMER_SPIRIT - TIMER_DEMON) / (double) maximum) * width);
int otherMark = (int) ((TIMER_OTHER / (double) maximum) * width);
g2d.drawLine(demonMark, 0, demonMark, height);
g2d.drawLine(otherMark, 0, otherMark, height);
}
}
// Test the SmudgeTimerPanel in its own JFrame
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Smudge Timer Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
SmudgeTimerPanel timerPanel = new SmudgeTimerPanel();
frame.add(timerPanel);
frame.setVisible(true);
});
}
}
通过一些修改,我们解决了问题...
// change attribute type, because modifications to
// the Guide must be made from the event thread (EDT).
private javax.swing.Timer timer;
public void resetTimer( int timerDuration ) {
if( timer != null ) {
timer.stop();
}
remainingTime = timerDuration;
isRunning = true;
updateGhostLabel();
updateTimeDisplay();
progressBar.setMaximum( timerDuration );
progressBar.setValue( timerDuration );
statusLabel.setBackground( Color.GREEN );
// change initialization
timer = new javax.swing.Timer( 1000, e -> {
// delete "SwingUtilities.invokeLater( () -> {" it isn't necesary
if( remainingTime > 0 ) {
remainingTime --;
progressBar.setValue( remainingTime );
updateTimeDisplay();
updateGhostLabel();
// call "repaint" to update the interface
repaint();
}
else {
stopTimer();
}
} );
timer.start();
}
public void stopTimer() {
if( timer != null ) {
// change "cancel()" to "stop()"
timer.stop();
}
isRunning = false;
statusLabel.setBackground( Color.RED );
}